Merge pull request #536 from ONLYOFFICE/feature/VDR-indexing

Feature/vdr indexing
This commit is contained in:
Alexey Safronov 2024-07-11 11:45:53 +04:00 committed by GitHub
commit 1bfa73ac4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
63 changed files with 1638 additions and 230 deletions

View File

@ -66,6 +66,7 @@
"EmptyScreenFolder": "No docs here yet",
"EnableLink": "Enable link",
"EnableNotifications": "Enable notifications",
"ErrorChangeIndex": "Error when changing index. The problem may be caused on the server side. Reload the page or check your Internet connection settings.",
"ExcludeSubfolders": "Exclude subfolders",
"ExportRoomIndex": "Export room index",
"ExportRoomIndexAlreadyInProgressError": "Room index export is already in progress. Please wait until the current export is completed to start the new one.",
@ -88,6 +89,7 @@
"GoToPersonal": "Go to Documents",
"Images": "Images",
"InviteUsersInRoom": "Invite users in room",
"Index": "Index",
"LeaveRoomDescription": "You are the owner of this room. Before you leave the room, you must transfer the owner's role to another user.",
"LeaveTheRoom": "Leave the room",
"LeftAndAppointNewOwner": "You have left the room and appointed a new owner",
@ -154,6 +156,8 @@
"RoomsRemoved": "Rooms removed",
"RoomsUnpinned": "Rooms unpinned: {{count}}",
"RoomUnpinned": "Room unpinned",
"ReorderIndex": "The Reorder action will remove spaces in the indexes. The files will be re-indexed in order with offset to fill in missing indexes.",
"Reorder": "Reorder",
"SearchByContent": "Search by file contents",
"SendByEmail": "Send by email",
"ShareFolder": "Share folder",

View File

@ -65,6 +65,7 @@
"EmptyScreenFolder": "Здесь пока нет документов",
"EnableLink": "Активировать ссылку",
"EnableNotifications": "Включить уведомления",
"ErrorChangeIndex": "Ошибка при изменении индекса. Проблема может быть вызвана на стороне сервера. Перезагрузите страницу или проверьте настройки подключения к Интернету.",
"ExcludeSubfolders": "Исключить вложенные папки",
"FavoritesEmptyContainerDescription": "Чтобы добавить файлы в избранное или удалить их из этого списка, используйте контекстное меню.",
"FileContents": "Содержимое файла",
@ -84,6 +85,7 @@
"GoToPersonal": "Перейти к Документам",
"Images": "Изображения",
"InviteUsersInRoom": "Пригласить пользователей в комнату",
"Index": "Индекс",
"LeaveRoomDescription": "Вы являетесь новым владельцем комнаты. Перед тем, как покинуть комнату, вы должны передать роль владельца другому пользователю.",
"LeaveTheRoom": "Покинуть комнату",
"LeftAndAppointNewOwner": "Вы покинули комнату и назначили нового владельца",
@ -135,6 +137,8 @@
"RemoveFromList": "Убрать из списка",
"RestoreAll": "Восстановить все",
"RevokeLink": "Отозвать ссылку",
"ReorderIndex": "Действие «Изменить порядок» удалит пробелы в индексах. Файлы будут переиндексированы по порядку со смещением для заполнения недостающих индексов.",
"Reorder": "Изменить порядок",
"RoomAvailableViaExternalLink": "Комната доступна по внешней ссылке",
"RoomCreated": "Комната создана",
"RoomEmptyAtTheMoment": "В данный момент эта комната пуста.",

View File

@ -276,7 +276,9 @@ export default function withFileActions(WrappedFileItem) {
isDisabledDropItem,
isRecentTab,
canDrag,
isIndexUpdated,
} = this.props;
const { access, id } = item;
const isDragging =
@ -336,6 +338,7 @@ export default function withFileActions(WrappedFileItem) {
value={value}
displayShareButton={displayShareButton}
isPrivacy={isPrivacy}
isIndexUpdated={isIndexUpdated}
checkedProps={checkedProps}
dragging={dragging}
getContextModel={this.getContextModel}
@ -361,6 +364,7 @@ export default function withFileActions(WrappedFileItem) {
filesStore,
uploadDataStore,
contextOptionsStore,
indexingStore,
},
{ item, t },
) => {
@ -374,6 +378,7 @@ export default function withFileActions(WrappedFileItem) {
uploadEmptyFolders,
} = filesActionsStore;
const { setSharingPanelVisible } = dialogsStore;
const { updateSelection } = indexingStore;
const {
isPrivacyFolder,
isRecycleBinFolder,
@ -408,6 +413,10 @@ export default function withFileActions(WrappedFileItem) {
(x) => x.id === item.id && x.fileExst === item.fileExst,
);
const isIndexUpdated = !!updateSelection.find(
(x) => x.id === item.id && x.fileExst === item?.fileExst,
);
const isDisabledDropItem = item.security?.Create === false;
const draggable =
@ -505,6 +514,7 @@ export default function withFileActions(WrappedFileItem) {
currentDeviceType: settingsStore.currentDeviceType,
isDisabledDropItem,
isRecentTab,
isIndexUpdated,
canDrag: !dragIsDisabled,
};

View File

@ -93,6 +93,7 @@ const withHotkeys = (Component) => {
isGroupMenuBlocked,
isFormRoom,
isParentFolderFormRoom,
isIndexEditingMode,
} = props;
const navigate = useNavigate();
@ -178,7 +179,8 @@ const withHotkeys = (Component) => {
(e) => {
const someDialogIsOpen = checkDialogsOpen();
if (e.shiftKey || e.ctrlKey || someDialogIsOpen) return;
if (e.shiftKey || e.ctrlKey || someDialogIsOpen || isIndexEditingMode)
return;
switch (e.key) {
case "ArrowDown":
@ -423,6 +425,7 @@ const withHotkeys = (Component) => {
selectedFolderStore,
userStore,
currentTariffStatusStore,
indexingStore,
}) => {
const {
setSelected,
@ -530,6 +533,7 @@ const withHotkeys = (Component) => {
isTrashFolder,
isArchiveFolder,
isRoomsFolder,
isIndexEditingMode: indexingStore.isIndexEditingMode,
selection,
setFavoriteAction,

View File

@ -150,6 +150,7 @@ export default function withQuickButtons(WrappedComponent) {
isPublicRoom,
isPersonalRoom,
isArchiveFolder,
isIndexEditingMode,
currentDeviceType,
roomLifetime,
} = this.props;
@ -177,6 +178,7 @@ export default function withQuickButtons(WrappedComponent) {
folderCategory={folderCategory}
onCopyPrimaryLink={this.onCopyPrimaryLink}
isArchiveFolder={isArchiveFolder}
isIndexEditingMode={isIndexEditingMode}
currentDeviceType={currentDeviceType}
showLifetimeIcon={showLifetimeIcon}
expiredDate={expiredDate}
@ -202,6 +204,7 @@ export default function withQuickButtons(WrappedComponent) {
treeFoldersStore,
filesStore,
infoPanelStore,
indexingStore,
}) => {
const { lockFileAction, setFavoriteAction, onSelectItem } =
filesActionsStore;
@ -213,6 +216,8 @@ export default function withQuickButtons(WrappedComponent) {
isArchiveFolder,
} = treeFoldersStore;
const { isIndexEditingMode } = indexingStore;
const { setSharingPanelVisible } = dialogsStore;
const folderCategory =
@ -238,6 +243,7 @@ export default function withQuickButtons(WrappedComponent) {
isArchiveFolder,
getPrimaryFileLink,
setShareChanged,
isIndexEditingMode,
roomLifetime: infoPanelRoom?.lifetime,
};
},

View File

@ -75,10 +75,12 @@ const Item = ({
iconBadge,
folderId,
currentColorScheme,
isIndexEditingMode,
}) => {
const [isDragActive, setIsDragActive] = useState(false);
const isDragging = dragging ? showDragItems(item) : false;
const isDragging =
dragging && !isIndexEditingMode ? showDragItems(item) : false;
let value = "";
if (isDragging) value = `${item.id} dragging`;
@ -221,6 +223,7 @@ const Items = ({
currentDeviceType,
folderAccess,
currentColorScheme,
isIndexEditingMode,
}) => {
const getEndOfBlock = React.useCallback((item) => {
switch (item.key) {
@ -343,6 +346,7 @@ const Items = ({
iconBadge={iconBadge}
folderId={`document_catalog-${FOLDER_NAMES[item.rootFolderType]}`}
currentColorScheme={currentColorScheme}
isIndexEditingMode={isIndexEditingMode}
/>
);
});
@ -395,6 +399,7 @@ const Items = ({
firstLoad,
activeItemId,
emptyTrashInProgress,
isIndexEditingMode,
],
);
@ -421,6 +426,7 @@ export default inject(
clientLoadingStore,
userStore,
settingsStore,
indexingStore,
}) => {
const { isCommunity, isPaymentPageAvailable, currentDeviceType } =
authStore;
@ -437,6 +443,8 @@ export default inject(
startDrag,
} = filesStore;
const { isIndexEditingMode } = indexingStore;
const { firstLoad } = clientLoadingStore;
const { startUpload } = uploadDataStore;
@ -488,6 +496,7 @@ export default inject(
currentDeviceType,
folderAccess,
currentColorScheme,
isIndexEditingMode,
};
},
)(withTranslation(["Files", "Common", "Translations"])(observer(Items)));

View File

@ -76,6 +76,7 @@ import LeaveRoomDialog from "../dialogs/LeaveRoomDialog";
import ChangeRoomOwnerPanel from "../panels/ChangeRoomOwnerPanel";
import { CreatedPDFFormDialog } from "../dialogs/CreatedPDFFormDialog";
import { PDFFormEditingDialog } from "../dialogs/PDFFormEditingDialog";
import ReorderIndexDialog from "../dialogs/ReorderIndexDialog";
import LifetimeDialog from "../dialogs/LifetimeDialog";
import { SharePDFFormDialog } from "../dialogs/SharePDFFormDialog";
@ -133,6 +134,7 @@ const Panels = (props) => {
shareFolderDialogVisible,
pdfFormEditVisible,
selectFileFormRoomOpenRoot,
reorderDialogVisible,
} = props;
const [createPDFFormFile, setCreatePDFFormFile] = useState({
@ -347,6 +349,7 @@ const Panels = (props) => {
<ChangeRoomOwnerPanel key="change-room-owner" />
),
shareFolderDialogVisible && <ShareFolderDialog key="share-folder-dialog" />,
reorderDialogVisible && <ReorderIndexDialog key="reorder-index-dialog" />,
createPDFFormFile.visible && (
<CreatedPDFFormDialog
key="created-pdf-form-dialog"
@ -418,6 +421,7 @@ export default inject(
shareFolderDialogVisible,
pdfFormEditVisible,
selectFileFormRoomOpenRoot,
reorderDialogVisible,
} = dialogsStore;
const { preparationPortalDialogVisible } = backup;
@ -487,6 +491,7 @@ export default inject(
shareFolderDialogVisible,
pdfFormEditVisible,
selectFileFormRoomOpenRoot,
reorderDialogVisible,
};
},
)(observer(Panels));

View File

@ -81,6 +81,7 @@ const QuickButtons = (props) => {
onClickShare,
isPersonalRoom,
isArchiveFolder,
isIndexEditingMode,
currentDeviceType,
showLifetimeIcon,
expiredDate,
@ -163,83 +164,85 @@ const QuickButtons = (props) => {
return (
<StyledQuickButtons className="badges additional-badges badges__quickButtons">
{showLifetimeIcon && (
{!isIndexEditingMode && (
<>
<ColorTheme
themeId={ThemeId.IconButton}
iconName={LifetimeReactSvgUrl}
className="badge file-lifetime icons-group"
size={sizeQuickButton}
isClickable
isDisabled={isDisabled}
data-tooltip-id="lifetimeTooltip"
color={theme.filesQuickButtons.lifeTimeColor}
/>
{showLifetimeIcon && (
<>
<ColorTheme
themeId={ThemeId.IconButton}
iconName={LifetimeReactSvgUrl}
className="badge file-lifetime icons-group"
size={sizeQuickButton}
isClickable
isDisabled={isDisabled}
data-tooltip-id="lifetimeTooltip"
color={theme.filesQuickButtons.lifeTimeColor}
/>
<Tooltip
id="lifetimeTooltip"
place="bottom"
getContent={getTooltipContent}
maxWidth="300px"
/>
</>
)}
<Tooltip
id="lifetimeTooltip"
place="bottom"
getContent={getTooltipContent}
maxWidth="300px"
/>
</>
)}
{isAvailableLockFile && (
<ColorTheme
themeId={ThemeId.IconButton}
iconName={iconLock}
className="badge lock-file icons-group"
size={sizeQuickButton}
data-id={id}
data-locked={locked ? true : false}
onClick={onClickLock}
color={colorLock}
isDisabled={isDisabled}
hoverColor={theme.filesQuickButtons.sharedColor}
title={t("UnblockVersion")}
/>
)}
{isAvailableDownloadFile && (
<ColorTheme
themeId={ThemeId.IconButton}
iconName={FileActionsDownloadReactSvgUrl}
className="badge download-file icons-group"
size={sizeQuickButton}
onClick={onClickDownload}
color={colorLock}
isDisabled={isDisabled}
hoverColor={theme.filesQuickButtons.sharedColor}
title={t("Common:Download")}
/>
)}
{showCopyLinkIcon && (
<ColorTheme
themeId={ThemeId.IconButton}
iconName={LinkReactSvgUrl}
className="badge copy-link icons-group"
size={sizeQuickButton}
onClick={onCopyPrimaryLink}
color={colorLock}
isDisabled={isDisabled}
hoverColor={theme.filesQuickButtons.sharedColor}
title={t("Files:CopySharedLink")}
/>
)}
{isAvailableShareFile && (
<ColorTheme
themeId={ThemeId.IconButton}
iconName={LinkReactSvgUrl}
className="badge copy-link icons-group"
size={sizeQuickButton}
onClick={onClickShare}
color={colorShare}
isDisabled={isDisabled}
hoverColor={theme.filesQuickButtons.sharedColor}
title={t("Files:CopySharedLink")}
/>
)}
{/* {fileExst && !isTrashFolder && displayBadges && (
{isAvailableLockFile && (
<ColorTheme
themeId={ThemeId.IconButton}
iconName={iconLock}
className="badge lock-file icons-group"
size={sizeQuickButton}
data-id={id}
data-locked={locked ? true : false}
onClick={onClickLock}
color={colorLock}
isDisabled={isDisabled}
hoverColor={theme.filesQuickButtons.sharedColor}
title={t("UnblockVersion")}
/>
)}
{isAvailableDownloadFile && (
<ColorTheme
themeId={ThemeId.IconButton}
iconName={FileActionsDownloadReactSvgUrl}
className="badge download-file icons-group"
size={sizeQuickButton}
onClick={onClickDownload}
color={colorLock}
isDisabled={isDisabled}
hoverColor={theme.filesQuickButtons.sharedColor}
title={t("Common:Download")}
/>
)}
{showCopyLinkIcon && (
<ColorTheme
themeId={ThemeId.IconButton}
iconName={LinkReactSvgUrl}
className="badge copy-link icons-group"
size={sizeQuickButton}
onClick={onCopyPrimaryLink}
color={colorLock}
isDisabled={isDisabled}
hoverColor={theme.filesQuickButtons.sharedColor}
title={t("Files:CopySharedLink")}
/>
)}
{isAvailableShareFile && (
<ColorTheme
themeId={ThemeId.IconButton}
iconName={LinkReactSvgUrl}
className="badge copy-link icons-group"
size={sizeQuickButton}
onClick={onClickShare}
color={colorShare}
isDisabled={isDisabled}
hoverColor={theme.filesQuickButtons.sharedColor}
title={t("Files:CopySharedLink")}
/>
)}
{/* {fileExst && !isTrashFolder && displayBadges && (
<ColorTheme
themeId={ThemeId.IconButton}
iconName={iconFavorite}
@ -253,6 +256,8 @@ const QuickButtons = (props) => {
hoverColor={theme.filesQuickButtons.hoverColor}
/>
)} */}
</>
)}
</StyledQuickButtons>
);
};

View File

@ -41,10 +41,12 @@ export default inject(
settingsStore,
dialogsStore,
infoPanelStore,
indexingStore,
}: {
settingsStore: any;
dialogsStore: any;
infoPanelStore: any;
indexingStore: any;
}) => {
const {
isDesktopClient: isDesktop,
@ -59,6 +61,8 @@ export default inject(
const { isVisible, isMobileHidden, setIsVisible, getCanDisplay } =
infoPanelStore;
const { isIndexEditingMode } = indexingStore;
const { createRoomDialogVisible, invitePanelOptions } = dialogsStore;
const canDisplay = getCanDisplay();
@ -71,7 +75,7 @@ export default inject(
return {
isDesktop,
currentDeviceType,
isInfoPanelVisible: isVisible,
isInfoPanelVisible: isVisible && !isIndexEditingMode,
isMobileHidden,
setIsInfoPanelVisible: setIsVisible,
canDisplay,

View File

@ -0,0 +1,128 @@
// (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 { inject, observer } from "mobx-react";
import { useTranslation } from "react-i18next";
import { TTranslation } from "@docspace/shared/types";
import {
ModalDialog,
ModalDialogType,
} from "@docspace/shared/components/modal-dialog";
import { Text } from "@docspace/shared/components/text";
import { Button, ButtonSize } from "@docspace/shared/components/button";
import { isMobile } from "react-device-detect";
import DialogsStore from "SRC_DIR/store/DialogsStore";
import SelectedFolderStore from "SRC_DIR/store/SelectedFolderStore";
import FilesActionsStore from "SRC_DIR/store/FilesActionsStore";
import { TSelectedFolder } from "@docspace/client/src/store/SelectedFolderStore";
export interface ReorderIndexDialogProps {
reorder: (id: number | string | null, t: TTranslation) => void;
setIsVisible: (visible: boolean) => void;
visible: boolean;
selectedFolder: TSelectedFolder;
}
const ReorderIndexDialog = ({
visible,
setIsVisible,
reorder,
selectedFolder,
}: ReorderIndexDialogProps) => {
const { t, ready } = useTranslation(["Files", "Common"]);
const onClose = () => {
setIsVisible(false);
};
const onReorder = () => {
reorder(selectedFolder?.id, t);
setIsVisible(false);
};
return (
<ModalDialog
displayType={ModalDialogType.modal}
isLarge
isLoading={!ready}
visible={visible}
onClose={onClose}
>
<ModalDialog.Header>{t("Common:Warning")}</ModalDialog.Header>
<ModalDialog.Body>
<Text fontSize="13px" fontWeight={400} noSelect>
{t("Files:ReorderIndex")}
</Text>
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
id="create-room"
key="OkButton"
label={t("Files:Reorder")}
size={ButtonSize.normal}
primary
onClick={onReorder}
scale={isMobile}
/>
<Button
id="cancel-share-folder"
key="CancelButton"
label={t("Common:CancelButton")}
size={ButtonSize.normal}
onClick={onClose}
scale={isMobile}
/>
</ModalDialog.Footer>
</ModalDialog>
);
};
export default inject(
({
dialogsStore,
filesActionsStore,
selectedFolderStore,
}: {
dialogsStore: DialogsStore;
filesActionsStore: FilesActionsStore;
selectedFolderStore: SelectedFolderStore;
}) => {
const { reorderDialogVisible, setReorderDialogVisible } = dialogsStore;
const selectedFolder = selectedFolderStore.getSelectedFolder();
const { reorder } = filesActionsStore;
return {
visible: reorderDialogVisible,
setIsVisible: setReorderDialogVisible,
reorder,
selectedFolder,
};
},
)(observer(ReorderIndexDialog));

View File

@ -174,6 +174,7 @@ class DetailsHelper {
"Date modified",
"Last modified by",
"Creation date",
this.item.order && "Index",
]
: [
"Owner",
@ -186,6 +187,7 @@ class DetailsHelper {
"Creation date",
this.item.expired && "Lifetime ends",
"Versions",
this.item.order && "Index",
"Comments",
]
).filter((nP) => !!nP);
@ -220,6 +222,9 @@ class DetailsHelper {
case "Lifetime ends":
return this.t("LifetimeEnds");
case "Index":
return this.t("Files:Index");
case "Versions":
return this.t("InfoPanel:Versions");
case "Comments":
@ -245,6 +250,9 @@ class DetailsHelper {
case "Location":
return this.getItemLocation();
case "Index":
return this.getItemIndex();
case "Type":
return this.getItemType();
case "Storage Type":
@ -337,6 +345,10 @@ class DetailsHelper {
return text(this.item.contentLength);
};
getItemIndex = () => {
return text(this.item.order);
};
getItemDateModified = () => {
return text(parseAndFormatDate(this.item.updated, this.culture));
};

View File

@ -36,6 +36,7 @@ import { IconButton } from "@docspace/shared/components/icon-button";
import { StyledTitle } from "../../../styles/common";
import { RoomIcon } from "@docspace/shared/components/room-icon";
import RoomsContextBtn from "./context-btn";
import { getDefaultAccessUser } from "@docspace/shared/utils/getDefaultAccessUser";
import CalendarComponent from "../Calendar";
import {
FolderType,

View File

@ -74,6 +74,8 @@ const FilesRowContainer = ({
withPaging,
highlightFile,
currentDeviceType,
isIndexEditingMode,
changeIndex,
}) => {
useViewEffect({
view: viewAs,
@ -93,9 +95,11 @@ const FilesRowContainer = ({
sectionWidth={sectionWidth}
isRooms={isRooms}
isTrashFolder={isTrashFolder}
changeIndex={changeIndex}
isHighlight={
highlightFile.id == item.id && highlightFile.isExst === !item.fileExst
}
isIndexEditingMode={isIndexEditingMode}
/>
));
}, [
@ -124,7 +128,14 @@ const FilesRowContainer = ({
};
export default inject(
({ filesStore, settingsStore, infoPanelStore, treeFoldersStore }) => {
({
filesStore,
settingsStore,
infoPanelStore,
treeFoldersStore,
indexingStore,
filesActionsStore,
}) => {
const {
filesList,
viewAs,
@ -138,6 +149,7 @@ export default inject(
const { isVisible: infoPanelVisible } = infoPanelStore;
const { isRoomsFolder, isArchiveFolder, isTrashFolder } = treeFoldersStore;
const { withPaging, currentDeviceType } = settingsStore;
const { isIndexEditingMode } = indexingStore;
const isRooms = isRoomsFolder || isArchiveFolder;
@ -154,6 +166,8 @@ export default inject(
withPaging,
highlightFile,
currentDeviceType,
isIndexEditingMode,
changeIndex: filesActionsStore.changeIndex,
};
},
)(observer(FilesRowContainer));

View File

@ -216,6 +216,7 @@ const FilesRowContent = ({
isDefaultRoomsQuotaSet,
isStatisticsAvailable,
showStorageInfo,
isIndexing,
}) => {
const {
contentLength,
@ -230,6 +231,7 @@ const FilesRowContent = ({
tags,
quotaLimit,
usedSpace,
order,
} = item;
const contentComponent = () => {
@ -323,6 +325,17 @@ const FilesRowContent = ({
{!isRoom && !isRooms && quickButtons}
</div>
{isIndexing && (
<Text
containerMinWidth="200px"
containerWidth="15%"
fontSize="12px"
fontWeight={400}
className="row_update-text"
>
{`${t("Files:Index")} ${order}`}
</Text>
)}
{mainInfo && (
<Text
containerMinWidth="200px"
@ -352,7 +365,13 @@ const FilesRowContent = ({
};
export default inject(
({ currentQuotaStore, settingsStore, treeFoldersStore, filesStore }) => {
({
currentQuotaStore,
settingsStore,
treeFoldersStore,
filesStore,
indexingStore,
}) => {
const { filter, roomsFilter } = filesStore;
const { isRecycleBinFolder, isRoomsFolder, isArchiveFolder } =
treeFoldersStore;
@ -360,6 +379,8 @@ export default inject(
const isRooms = isRoomsFolder || isArchiveFolder;
const filterSortBy = isRooms ? roomsFilter.sortBy : filter.sortBy;
const { isIndexing } = indexingStore;
const { isDefaultRoomsQuotaSet, isStatisticsAvailable, showStorageInfo } =
currentQuotaStore;
return {
@ -369,6 +390,7 @@ export default inject(
isDefaultRoomsQuotaSet,
isStatisticsAvailable,
showStorageInfo,
isIndexing,
};
},
)(

View File

@ -68,7 +68,10 @@ const StyledWrapper = styled.div`
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
margin-top: -1px;
${(props) => (props.checked || props.isActive) && checkedStyle};
${(props) =>
(props.checked || props.isActive) &&
!props.isIndexEditingMode &&
checkedStyle};
${(props) =>
(props.checked || props.isActive) &&
props.isFirstElem &&
@ -77,9 +80,20 @@ const StyledWrapper = styled.div`
`${props.theme.filesSection.tableView.row.borderColor} !important`};
`};
${(props) =>
props.isIndexUpdated &&
css`
background: ${(props) =>
props.isIndexEditingMode
? `${props.theme.filesSection.tableView.row.indexUpdate} !important`
: `${props.theme.filesSection.tableView.row.backgroundActive} !important`};
${marginStyles}
`}
${(props) =>
!isMobile &&
!props.isDragging &&
!props.isIndexEditingMode &&
css`
:hover {
cursor: pointer;
@ -87,6 +101,18 @@ const StyledWrapper = styled.div`
}
`};
${(props) =>
!isMobile &&
props.isIndexEditingMode &&
css`
:hover {
cursor: pointer;
background: ${(props) =>
props.theme.filesSection.tableView.row.indexActive};
${marginStyles}
}
`};
${(props) =>
props.showHotkeyBorder &&
css`
@ -335,6 +361,7 @@ StyledSimpleFilesRow.defaultProps = { theme: Base };
const SimpleFilesRow = (props) => {
const {
t,
item,
sectionWidth,
dragging,
@ -368,6 +395,9 @@ const SimpleFilesRow = (props) => {
itemIndex,
badgeUrl,
canDrag,
isIndexEditingMode,
changeIndex,
isIndexUpdated,
} = props;
const isMobileDevice = isMobileUtile();
@ -377,6 +407,10 @@ const SimpleFilesRow = (props) => {
const withAccess = item.security?.Lock;
const isSmallContainer = sectionWidth <= 500;
const onChangeIndex = (action) => {
return changeIndex(action, item, t);
};
const element = (
<ItemIcon
id={item.id}
@ -433,6 +467,8 @@ const SimpleFilesRow = (props) => {
checked={checkedProps}
isActive={isActive}
showHotkeyBorder={showHotkeyBorder}
isIndexEditingMode={isIndexEditingMode}
isIndexUpdated={isIndexUpdated}
isFirstElem={itemIndex === 0}
isHighlight={isHighlight}
>
@ -468,6 +504,8 @@ const SimpleFilesRow = (props) => {
contextButtonSpacerWidth={displayShareButton}
dragging={dragging && isDragging}
isDragging={dragging}
isIndexEditingMode={isIndexEditingMode}
onChangeIndex={onChangeIndex}
isActive={isActive}
inProgress={inProgress}
isThirdPartyFolder={item.isThirdPartyFolder}

View File

@ -74,11 +74,19 @@ const contextMenuWrapperDraggingStyle = css`
const StyledTableRow = styled(TableRow)`
.table-container_cell:not(.table-container_element-wrapper) {
border-top: ${(props) =>
!props.isIndexEditingMode &&
`1px solid ${props.theme.filesSection.tableView.row.borderColor}`};
margin-top: -1px;
border-left: 0; //for Safari
border-right: 0; //for Safari
}
${(props) =>
props.isIndexEditingMode &&
css`
.table-container_element {
display: flex !important;
}
`}
.table-container_cell:not(.table-container_element-wrapper) {
height: auto;
@ -107,14 +115,18 @@ const StyledTableRow = styled(TableRow)`
`}
${(props) =>
!props.isDragging &&
!props.isIndexUpdated &&
css`
:hover {
.table-container_cell {
cursor: pointer;
background: ${(props) =>
`${props.theme.filesSection.tableView.row.backgroundActive} !important`};
props.isIndexEditingMode
? `${props.theme.filesSection.tableView.row.indexActive} !important`
: `${props.theme.filesSection.tableView.row.backgroundActive} !important`};
}
.table-container_file-name-cell {
.table-container_file-name-cell,
.table-container_index-cell {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
@ -126,6 +138,7 @@ const StyledTableRow = styled(TableRow)`
padding-left: 24px;
`}
}
.table-container_row-context-menu-wrapper {
${(props) =>
props.theme.interfaceDirection === "rtl"
@ -140,9 +153,49 @@ const StyledTableRow = styled(TableRow)`
}
}
`}
${(props) =>
props.isIndexUpdated &&
css`
.table-container_cell {
cursor: pointer;
background: ${(props) =>
props.isIndexEditingMode
? `${props.theme.filesSection.tableView.row.indexUpdate} !important`
: `${props.theme.filesSection.tableView.row.backgroundActive} !important`};
}
.table-container_file-name-cell,
.table-container_index-cell {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: -24px;
padding-right: 24px;
`
: css`
margin-left: -24px;
padding-left: 24px;
`}
}
.table-container_row-context-menu-wrapper {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: -20px;
padding-left: 20px !important;
`
: css`
margin-right: -20px;
padding-right: 20px !important;
`}
}
`}
.table-container_cell {
background: ${(props) =>
(props.checked || props.isActive) &&
!props.isIndexUpdated &&
!props.isIndexEditingMode &&
`${props.theme.filesSection.tableView.row.backgroundActive} !important`};
cursor: ${(props) =>
!props.isThirdPartyFolder &&
@ -219,7 +272,8 @@ const StyledTableRow = styled(TableRow)`
width: 12px;
}
.table-container_file-name-cell {
.table-container_file-name-cell,
.table-container_index-cell {
${(props) =>
props.showHotkeyBorder &&
css`
@ -239,6 +293,30 @@ const StyledTableRow = styled(TableRow)`
${(props) => props.dragging && rowCheckboxDraggingStyle};
}
.table-container_element-wrapper {
${(props) =>
props.isIndexing &&
css`
margin-left: 0px;
padding-left: 0px;
`}
}
${(props) =>
props.isIndexing &&
css`
.table-container_file-name-cell {
margin-left: 0px !important;
padding-left: 0px !important;
}
&:hover {
.table-container_file-name-cell {
margin-left: 0px !important;
padding-left: 0px !important;
}
}
`}
.table-container_row-context-menu-wrapper {
${(props) =>
props.theme.interfaceDirection === "rtl"
@ -281,6 +359,10 @@ const StyledTableRow = styled(TableRow)`
padding-inline: 0 8px;
}
.item-file-name-index {
text-decoration: none;
}
${(props) =>
props.isHighlight &&
css`
@ -298,7 +380,8 @@ const StyledTableRow = styled(TableRow)`
}
}
.table-container_file-name-cell {
.table-container_file-name-cell,
.table-container_index-cell {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`

View File

@ -70,19 +70,28 @@ const StyledTableContainer = styled(TableContainer)`
.table-container_file-name-cell {
${fileNameCss}
}
.table-container_index-cell {
${fileNameCss}
}
.table-container_row-context-menu-wrapper {
${contextCss}
}
}
.table-container_index-cell {
margin-right: 0;
padding-right: 0;
}
.table-row-selected + .table-row-selected {
.table-row {
.table-container_file-name-cell,
.table-container_index-cell,
.table-container_row-context-menu-wrapper {
border-image-slice: 1;
}
.table-container_file-name-cell {
.table-container_file-name-cell,
.table-container_index-cell {
${fileNameCss}
border-left: 0; //for Safari macOS
border-right: 0; //for Safari macOS
@ -101,7 +110,8 @@ const StyledTableContainer = styled(TableContainer)`
.files-item:not(.table-row-selected) + .table-row-selected {
.table-row {
.table-container_file-name-cell {
.table-container_file-name-cell,
.table-container_index-cell {
${fileNameCss}
}
@ -133,11 +143,14 @@ const Table = ({
filterTotal,
isRooms,
isTrashFolder,
isIndexEditingMode,
withPaging,
columnStorageName,
columnInfoPanelStorageName,
highlightFile,
currentDeviceType,
onEditIndex,
isIndexing,
}) => {
const [tagCount, setTagCount] = React.useState(null);
const [hideColumns, setHideColumns] = React.useState(false);
@ -200,6 +213,9 @@ const Table = ({
item={item}
itemIndex={index}
index={index}
onEditIndex={onEditIndex}
isIndexEditingMode={isIndexEditingMode}
isIndexing={isIndexing}
setFirsElemChecked={setFirsElemChecked}
setHeaderBorder={setHeaderBorder}
theme={theme}
@ -223,6 +239,8 @@ const Table = ({
highlightFile.id,
highlightFile.isExst,
isTrashFolder,
isIndexEditingMode,
isIndexing,
]);
return (
@ -235,6 +253,7 @@ const Table = ({
navigate={navigate}
location={location}
isRooms={isRooms}
isIndexing={isIndexing}
filesList={filesList}
/>
@ -246,6 +265,7 @@ const Table = ({
itemCount={filterTotal}
useReactWindow={!withPaging}
infoPanelVisible={infoPanelVisible}
isIndexEditingMode={isIndexEditingMode}
columnInfoPanelStorageName={columnInfoPanelStorageName}
itemHeight={48}
>
@ -264,6 +284,9 @@ export default inject(
tableStore,
userStore,
settingsStore,
indexingStore,
filesActionsStore,
}) => {
const { isVisible: infoPanelVisible } = infoPanelStore;
@ -285,6 +308,9 @@ export default inject(
highlightFile,
} = filesStore;
const { isIndexEditingMode, isIndexing } = indexingStore;
const { changeIndex } = filesActionsStore;
const { withPaging, theme, currentDeviceType } = settingsStore;
return {
@ -301,11 +327,14 @@ export default inject(
filterTotal: isRooms ? roomsFilterTotal : filterTotal,
isRooms,
isTrashFolder,
isIndexEditingMode,
isIndexing,
withPaging,
columnStorageName,
columnInfoPanelStorageName,
highlightFile,
currentDeviceType,
onEditIndex: changeIndex,
};
},
)(observer(Table));

View File

@ -54,6 +54,8 @@ class FilesTableHeader extends React.Component {
showStorageInfo,
isArchiveFolder,
tableStorageName,
isIndexing,
indexColumnSize,
} = this.props;
const defaultColumns = [];
@ -357,6 +359,17 @@ class FilesTableHeader extends React.Component {
defaultColumns.push(...columns);
}
if (isIndexing) {
defaultColumns.unshift({
key: "Index",
title: "#",
enable: this.props.indexColumnIsEnabled,
minWidth: indexColumnSize,
resizable: false,
isShort: true,
});
}
let columns = getColumns(defaultColumns);
const storageColumns = localStorage.getItem(tableStorageName);
const splitColumns = storageColumns && storageColumns.split(",");
@ -426,11 +439,15 @@ class FilesTableHeader extends React.Component {
columnInfoPanelStorageName,
isRecentTab,
isArchiveFolder,
isIndexEditingMode,
showStorageInfo,
indexColumnSize,
} = this.props;
if (
isArchiveFolder !== prevProps.isArchiveFolder ||
indexColumnSize !== prevProps.indexColumnSize ||
isIndexEditingMode !== prevProps.isIndexEditingMode ||
isRooms !== prevProps.isRooms ||
isTrashFolder !== prevProps.isTrashFolder ||
columnStorageName !== prevProps.columnStorageName ||
@ -543,6 +560,9 @@ class FilesTableHeader extends React.Component {
setHideColumns,
isFrame,
showSettings,
isIndexing,
isIndexEditingMode,
} = this.props;
const {
@ -568,7 +588,9 @@ class FilesTableHeader extends React.Component {
columnInfoPanelStorageName={columnInfoPanelStorageName}
sectionWidth={sectionWidth}
resetColumnsSize={resetColumnsSize}
sortingVisible={sortingVisible}
isIndexing={isIndexing}
sortingVisible={isIndexing ? false : sortingVisible}
isIndexEditingMode={isIndexEditingMode}
infoPanelVisible={infoPanelVisible}
useReactWindow={!withPaging}
tagRef={tagRef}
@ -592,11 +614,14 @@ export default inject(
clientLoadingStore,
infoPanelStore,
currentQuotaStore,
indexingStore,
}) => {
const { isVisible: infoPanelVisible } = infoPanelStore;
const { isDefaultRoomsQuotaSet, showStorageInfo } = currentQuotaStore;
const { isIndexEditingMode, isIndexing } = indexingStore;
const {
isHeaderChecked,
@ -607,6 +632,7 @@ export default inject(
headerBorder,
roomsFilter,
setRoomsFilter,
indexColumnSize,
} = filesStore;
const { isRecentTab, isArchiveFolder, isTrashFolder } = treeFoldersStore;
const withContent = canShare;
@ -627,6 +653,7 @@ export default inject(
roomColumnIsEnabled,
erasureColumnIsEnabled,
sizeColumnIsEnabled,
indexColumnIsEnabled,
sizeTrashColumnIsEnabled,
typeColumnIsEnabled,
typeTrashColumnIsEnabled,
@ -655,6 +682,8 @@ export default inject(
withContent,
sortingVisible,
isIndexing,
setIsLoading: clientLoadingStore.setIsSectionBodyLoading,
roomsFilter,
@ -678,6 +707,7 @@ export default inject(
roomColumnIsEnabled,
erasureColumnIsEnabled,
sizeColumnIsEnabled,
indexColumnIsEnabled,
sizeTrashColumnIsEnabled,
typeColumnIsEnabled,
typeTrashColumnIsEnabled,
@ -704,6 +734,9 @@ export default inject(
isDefaultRoomsQuotaSet,
showStorageInfo,
isArchiveFolder,
isIndexEditingMode,
indexColumnSize,
};
},
)(

View File

@ -65,6 +65,8 @@ const FilesTableRow = (props) => {
id,
isRooms,
isTrashFolder,
isIndexEditingMode,
isIndexing,
isHighlight,
hideColumns,
onDragOver,
@ -72,7 +74,10 @@ const FilesTableRow = (props) => {
badgeUrl,
isRecentTab,
canDrag,
onEditIndex,
isIndexUpdated,
} = props;
const { acceptBackground, background } = theme.dragAndDrop;
const element = (
@ -99,7 +104,7 @@ const FilesTableRow = (props) => {
const dragStyles = {
style: {
background:
dragging && isDragging
dragging && isDragging && !isIndexEditingMode
? isDragActive
? acceptBackground
: background
@ -107,6 +112,10 @@ const FilesTableRow = (props) => {
},
};
const onChangeIndex = (action) => {
return onEditIndex(action, item, t);
};
const onDragOverEvent = (dragActive, e) => {
onDragOver && onDragOver(e);
@ -140,6 +149,13 @@ const FilesTableRow = (props) => {
? `${item.id}_${item.fileExst}`
: item.id ?? "";
const contextOptionProps = isIndexEditingMode
? {}
: {
contextOptions: item.contextOptions,
getContextModel,
};
return (
<StyledDragAndDrop
id={id}
@ -163,16 +179,18 @@ const FilesTableRow = (props) => {
selectionProp={selectionProp}
key={item.id}
fileContextClick={fileContextClick}
onClick={onMouseClick}
onClick={isIndexEditingMode ? () => {} : onMouseClick}
onChangeIndex={onChangeIndex}
isActive={isActive}
isIndexEditingMode={isIndexEditingMode}
inProgress={inProgress}
isFolder={item.isFolder}
onHideContextMenu={onHideContextMenu}
isThirdPartyFolder={item.isThirdPartyFolder}
onDoubleClick={onDoubleClick}
checked={checkedProps}
contextOptions={item.contextOptions}
getContextModel={getContextModel}
onDoubleClick={isIndexEditingMode ? () => {} : onDoubleClick}
checked={checkedProps || isIndexUpdated}
isIndexing={isIndexing}
isIndexUpdated={isIndexUpdated}
showHotkeyBorder={showHotkeyBorder}
title={
item.isFolder
@ -184,6 +202,7 @@ const FilesTableRow = (props) => {
hideColumns={hideColumns}
badgeUrl={badgeUrl}
canDrag={canDrag}
{...contextOptionProps}
>
{isRooms ? (
<RoomsRowDataComponent

View File

@ -27,6 +27,7 @@
import React from "react";
import { Link } from "@docspace/shared/components/link";
import { Checkbox } from "@docspace/shared/components/checkbox";
import { classNames } from "@docspace/shared/utils";
import { TableCell } from "@docspace/shared/components/table";
import { Loader } from "@docspace/shared/components/loader";
@ -40,6 +41,7 @@ const FileNameCell = ({
theme,
t,
inProgress,
isIndexEditingMode,
}) => {
const { title, viewAccessibility } = item;
@ -49,6 +51,9 @@ const FileNameCell = ({
const isMedia = viewAccessibility?.ImageView || viewAccessibility?.MediaView;
const indexingClass = isIndexEditingMode ? "item-file-name-index" : "";
const linkProps = isIndexEditingMode ? null : { ...linkStyles };
return (
<>
{inProgress ? (
@ -59,31 +64,35 @@ const FileNameCell = ({
/>
) : (
<TableCell
className="table-container_element-wrapper"
className={classNames("table-container_element-wrapper", {
["table-container-index"]: isIndexEditingMode,
})}
style={{background: "none !important"}}
hasAccess={true}
checked={checked}
>
<div className="table-container_element-container">
<div className="table-container_element">{element}</div>
<Checkbox
className="table-container_row-checkbox"
onChange={onChange}
isChecked={checked}
title={t("Common:TitleSelectFile")}
/>
{!isIndexEditingMode && (
<Checkbox
className="table-container_row-checkbox"
onChange={onChange}
isChecked={checked}
title={t("Common:TitleSelectFile")}
/>
)}
</div>
</TableCell>
)}
<Link
type="page"
title={title}
fontWeight="600"
fontSize="13px"
{...linkStyles}
color={theme.filesSection.tableView.fileName.linkColor}
isTextOverflow
className="item-file-name"
{...linkProps}
className={`item-file-name ${indexingClass}`}
dir="auto"
>
{titleWithoutExt}

View File

@ -0,0 +1,46 @@
// (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 { StyledText } from "./CellStyles";
const IndexCell = ({ t, item, sideColor }) => {
const { order } = item;
return (
<StyledText
color={sideColor}
fontSize="12px"
fontWeight={600}
title={order}
style={{ marginRight: 0 }}
>
{order}
</StyledText>
);
};
export default IndexCell;

View File

@ -32,7 +32,9 @@ import TypeCell from "./TypeCell";
import AuthorCell from "./AuthorCell";
import DateCell from "./DateCell";
import SizeCell from "./SizeCell";
import IndexCell from "./IndexCell";
import { classNames, getLastColumn } from "@docspace/shared/utils";
import { RoomsType } from "@docspace/shared/enums";
import {
StyledBadgesContainer,
StyledQuickButtonsContainer,
@ -45,6 +47,7 @@ const RowDataComponent = (props) => {
modifiedColumnIsEnabled,
sizeColumnIsEnabled,
typeColumnIsEnabled,
indexColumnIsEnabled,
quickButtonsColumnIsEnabled,
dragStyles,
@ -58,6 +61,8 @@ const RowDataComponent = (props) => {
showHotkeyBorder,
badgesComponent,
quickButtonsComponent,
isIndexing,
tableStorageName,
} = props;
@ -65,6 +70,24 @@ const RowDataComponent = (props) => {
return (
<>
{indexColumnIsEnabled && isIndexing && (
<TableCell
className={classNames(
selectionProp?.className,
"table-container_index-cell",
)}
style={
!indexColumnIsEnabled ? { background: "none" } : dragStyles.style
}
value={value}
>
<IndexCell
sideColor={theme.filesSection.tableView.row.sideColor}
{...props}
/>
</TableCell>
)}
<TableCell
{...dragStyles}
className={classNames(
@ -214,24 +237,30 @@ const RowDataComponent = (props) => {
);
};
export default inject(({ tableStore }) => {
export default inject(({ tableStore, indexingStore }) => {
const {
authorColumnIsEnabled,
createdColumnIsEnabled,
modifiedColumnIsEnabled,
sizeColumnIsEnabled,
indexColumnIsEnabled,
typeColumnIsEnabled,
quickButtonsColumnIsEnabled,
tableStorageName,
} = tableStore;
const { isIndexing } = indexingStore;
return {
authorColumnIsEnabled,
createdColumnIsEnabled,
modifiedColumnIsEnabled,
sizeColumnIsEnabled,
indexColumnIsEnabled,
typeColumnIsEnabled,
quickButtonsColumnIsEnabled,
isIndexing,
tableStorageName,
};
})(observer(RowDataComponent));

View File

@ -38,9 +38,13 @@ import withHotkeys from "../../../../HOCs/withHotkeys";
import { Consumer, isMobile, isTablet } from "@docspace/shared/utils";
import { isElementInViewport } from "@docspace/shared/utils/common";
import { DeviceType } from "@docspace/shared/enums";
import { DeviceType, VDRIndexingAction } from "@docspace/shared/enums";
const separatorStyles = `width: 100vw; position: absolute; height: 3px; z-index: 1;`;
const sectionClass = "section-wrapper-content";
let currentDroppable = null;
let droppableSeparator = null;
let isDragActive = false;
const SectionBodyContent = (props) => {
@ -71,6 +75,8 @@ const SectionBodyContent = (props) => {
isEmptyPage,
movingInProgress,
currentDeviceType,
isIndexEditingMode,
changeIndex,
} = props;
useEffect(() => {
@ -203,7 +209,23 @@ const SectionBodyContent = (props) => {
return;
}
droppableSeparator && droppableSeparator.remove();
const droppable = wrapperElement.closest(".droppable");
const tableItem = wrapperElement.closest(".table-list-item");
const styles = tableItem && window.getComputedStyle(tableItem);
const indexSeparatorNode = document.createElement("div");
indexSeparatorNode.classList.add("indexing-separator");
const parent = document.querySelector(
".ReactVirtualized__Grid__innerScrollContainer",
);
if (styles) {
indexSeparatorNode.setAttribute("style", separatorStyles);
indexSeparatorNode.style.top = styles.top;
}
if (currentDroppable !== droppable) {
if (currentDroppable) {
if (viewAs === "table") {
@ -213,12 +235,15 @@ const SectionBodyContent = (props) => {
for (let cl of classElements) {
cl.classList.remove("droppable-hover");
}
if (isIndexEditingMode) {
droppableSeparator.remove();
}
} else {
currentDroppable.classList.remove("droppable-hover");
}
}
currentDroppable = droppable;
droppableSeparator = indexSeparatorNode;
if (currentDroppable) {
if (viewAs === "table") {
const value = currentDroppable.getAttribute("value");
@ -231,17 +256,38 @@ const SectionBodyContent = (props) => {
cl.classList.add("droppable-hover");
}
}
if (isIndexEditingMode) {
parent.insertBefore(indexSeparatorNode, tableItem);
}
} else {
currentDroppable.classList.add("droppable-hover");
currentDroppable = droppable;
droppableSeparator = indexSeparatorNode;
}
}
} else if (isIndexEditingMode) {
droppableSeparator && droppableSeparator.remove();
const wrappedClass = wrapperElement && wrapperElement.className;
droppableSeparator = indexSeparatorNode;
if (wrappedClass === sectionClass) {
indexSeparatorNode.setAttribute(
"style",
separatorStyles + "bottom: 0px;",
);
return parent.append(indexSeparatorNode);
}
parent.insertBefore(indexSeparatorNode, tableItem);
}
};
const onMouseUp = (e) => {
setStartDrag(false);
droppableSeparator && droppableSeparator.remove();
setTimeout(() => {
isDragActive = false;
setDragging(false);
@ -254,17 +300,48 @@ const SectionBodyContent = (props) => {
const isDragging = splitValue && splitValue.includes("dragging");
const treeValue = isDragging ? splitValue[0] : null;
const elem = e.target.closest(".droppable");
const elem = isIndexEditingMode
? e.target.closest(".files-item") || e.target.closest(`.${sectionClass}`)
: e.target.closest(".droppable");
const title = elem && elem.dataset.title;
const value = elem && elem.getAttribute("value");
if ((!value && !treeValue) || isRecycleBinFolder || !isDragActive) {
if (
((!value && !treeValue) || isRecycleBinFolder || !isDragActive) &&
!isIndexEditingMode
) {
return;
}
const folderId = value
? value.split("_").slice(1, -3).join("_")
: treeValue;
onMoveTo(folderId, title);
if (!isIndexEditingMode) return onMoveTo(folderId, title);
if (filesList.length === 1) return;
const replaceableItemId = isNaN(+folderId) ? folderId : +folderId;
const replaceableItemType = value && value.split("_").slice(0, 1).join("_");
const isSectionTarget = elem && elem.className === sectionClass;
let replaceable;
const item = isSectionTarget
? filesList[filesList.length - 1]
: filesList.find((i) =>
replaceableItemType === "file"
? i.id === replaceableItemId && i.fileExst
: i.id === replaceableItemId,
);
replaceable = item;
if (item === filesList[filesList.length - 1] && !isSectionTarget) {
replaceable = filesList[filesList.length - 2];
}
if (!replaceable) return;
changeIndex(VDRIndexingAction.MoveIndex, replaceable, t);
return;
};
@ -340,6 +417,7 @@ export default inject(
treeFoldersStore,
filesActionsStore,
uploadDataStore,
indexingStore,
}) => {
const {
isEmptyFilesList,
@ -372,6 +450,7 @@ export default inject(
setTooltipPosition,
isRecycleBinFolder: treeFoldersStore.isRecycleBinFolder,
moveDragItems: filesActionsStore.moveDragItems,
changeIndex: filesActionsStore.changeIndex,
viewAs,
setSelection,
setBufferSelection,
@ -387,6 +466,7 @@ export default inject(
movingInProgress,
currentDeviceType: settingsStore.currentDeviceType,
isEmptyPage,
isIndexEditingMode: indexingStore.isIndexEditingMode,
};
},
)(

View File

@ -316,6 +316,8 @@ const SectionFilterContent = ({
isTrash,
userId,
isPersonalRoom,
isIndexing,
isIndexEditingMode,
providers,
@ -2663,6 +2665,8 @@ const SectionFilterContent = ({
isPeopleAccounts={isPeopleAccounts}
isGroupsAccounts={isGroupsAccounts}
isInsideGroup={isInsideGroup}
isIndexing={isIndexing}
isIndexEditingMode={isIndexEditingMode}
disableThirdParty={isTrash}
/>
);
@ -2681,6 +2685,7 @@ export default inject(
userStore,
settingsStore,
currentQuotaStore,
indexingStore,
}) => {
const {
filter,
@ -2720,6 +2725,7 @@ export default inject(
const { isVisible: infoPanelVisible } = infoPanelStore;
const { showStorageInfo, isDefaultRoomsQuotaSet } = currentQuotaStore;
const { isIndexing, isIndexEditingMode } = indexingStore;
const {
filterStore,
@ -2759,6 +2765,8 @@ export default inject(
isRooms,
isTrash,
isArchiveFolder,
isIndexing,
isIndexEditingMode,
setIsLoading: clientLoadingStore.setIsSectionBodyLoading,
showFilterLoader: clientLoadingStore.showFilterLoader,

View File

@ -51,6 +51,7 @@ import {
getCategoryUrl,
} from "SRC_DIR/helpers/utils";
import TariffBar from "SRC_DIR/components/TariffBar";
import IndexMenu from "../IndexHeader";
import { getLifetimePeriodTranslation } from "@docspace/shared/utils/common";
const StyledContainer = styled.div`
@ -172,6 +173,7 @@ const SectionHeaderContent = (props) => {
t,
isRoomsFolder,
security,
setIsIndexEditingMode,
tReady,
isInfoPanelVisible,
isRootFolder,
@ -185,6 +187,7 @@ const SectionHeaderContent = (props) => {
isArchiveFolder,
isEmptyFilesList,
isHeaderVisible,
isIndexEditingMode,
isHeaderChecked,
isHeaderIndeterminate,
showText,
@ -238,11 +241,13 @@ const SectionHeaderContent = (props) => {
onCreateAndCopySharedLink,
showNavigationButton,
startUpload,
reorder,
getFolderModel,
onCreateRoom,
onEmptyTrashAction,
getHeaderOptions,
setBufferSelection,
setReorderDialogVisible,
} = props;
const location = useLocation();
@ -407,6 +412,12 @@ const SectionHeaderContent = (props) => {
isMobileView: currentDeviceType === DeviceType.mobile,
};
const indexMenuProps = {
setIsIndexEditingMode,
t,
setReorderDialogVisible,
};
if (isAccountsPage && !(isGroupsPage && isRoomAdmin)) {
tableGroupMenuVisible =
(!isGroupsPage ? isAccountsHeaderVisible : isGroupsHeaderVisible) &&
@ -517,6 +528,8 @@ const SectionHeaderContent = (props) => {
>
{tableGroupMenuVisible ? (
<TableGroupMenu {...tableGroupMenuProps} withComboBox />
) : isIndexEditingMode ? (
<IndexMenu {...indexMenuProps} />
) : (
<div className="header-container">
<Navigation
@ -624,6 +637,8 @@ export default inject(
userStore,
settingsStore,
uploadDataStore,
indexingStore,
dialogsStore,
}) => {
const { startUpload } = uploadDataStore;
const isRoomAdmin = userStore.user?.isRoomAdmin;
@ -662,12 +677,15 @@ export default inject(
const { isRecycleBinFolder, isRoomsFolder, isArchiveFolder } =
treeFoldersStore;
const { setReorderDialogVisible } = dialogsStore;
const {
getHeaderMenu,
isGroupMenuBlocked,
moveToRoomsPage,
onClickBack,
moveToPublicRoom,
reorder,
} = filesActionsStore;
const { setIsVisible, isVisible } = infoPanelStore;
@ -731,6 +749,7 @@ export default inject(
getCheckboxItemLabel: getAccountsCheckboxItemLabel,
} = headerMenuStore;
const { isIndexEditingMode, setIsIndexEditingMode } = indexingStore;
const { setSelected: setAccountsSelected } = selectionStore;
const { isPublicRoom } = publicRoomStore;
@ -773,6 +792,8 @@ export default inject(
setIsInfoPanelVisible: setIsVisible,
isInfoPanelVisible: isVisible,
isHeaderVisible,
isIndexEditingMode,
setIsIndexEditingMode,
isHeaderIndeterminate,
isHeaderChecked,
isTabletView: settingsStore.isTabletView,
@ -835,11 +856,13 @@ export default inject(
onCreateAndCopySharedLink,
showNavigationButton,
startUpload,
reorder,
getFolderModel,
onCreateRoom,
onEmptyTrashAction,
getHeaderOptions,
setBufferSelection,
setReorderDialogVisible,
};
},
)(

View File

@ -0,0 +1,142 @@
// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import styled from "styled-components";
import {
mobile,
tablet,
getCorrectBorderRadius,
getCorrectFourValuesStyle,
} from "@docspace/shared/utils";
const StyledTableIndexMenu = styled.div`
position: relative;
background: ${(props) => props.theme.tableContainer.groupMenu.background};
border-bottom: ${(props) =>
props.theme.tableContainer.groupMenu.borderBottom};
box-shadow: ${(props) => props.theme.tableContainer.groupMenu.boxShadow};
border-radius: ${({ theme }) =>
getCorrectBorderRadius("0px 0px 6px 6px", theme.interfaceDirection)};
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
margin: 0px 0px 0px -20px;
padding: 0 20px 0 20px;
height: 68px;
z-index: 199;
@media ${tablet} {
height: 60px;
}
@media ${mobile} {
height: 48px;
padding: 0px 18px;
}
.table-header_index-separator {
${(props) =>
props.theme.interfaceDirection === "rtl"
? `border-left: ${props.theme.tableContainer.groupMenu.borderRight};`
: `border-right: ${props.theme.tableContainer.groupMenu.borderRight};`}
width: 1px;
height: 21px;
margin: ${({ theme }) =>
getCorrectFourValuesStyle("0 16px 0 20px", theme.interfaceDirection)};
@media ${tablet} {
height: 36px;
}
@media ${mobile} {
height: 20px;
}
}
.table-header_reorder-icon {
margin-right: 8px;
}
.table-header_index-container {
display: flex;
flex-direction: row;
align-items: center;
}
.table-header_reorder-container {
display: flex;
flex-direction: row;
align-items: center;
}
&:hover {
.table-header_reorder-container {
cursor: pointer;
}
}
.table-header_reorder-icon {
svg {
path[fill] {
fill: ${(props) => props.theme.button.color.base};
}
path[stroke] {
stroke: ${(props) => props.theme.button.color.base};
}
}
:hover {
svg {
path[fill] {
fill: ${(props) => props.theme.button.color.baseHover};
}
path[stroke] {
stroke: ${(props) => props.theme.button.color.baseHover};
}
}
}
:active {
svg {
path[fill] {
fill: ${(props) => props.theme.button.color.baseActive};
}
path[stroke] {
stroke: ${(props) => props.theme.button.color.baseActive};
}
}
}
}
`;
export { StyledTableIndexMenu };

View File

@ -0,0 +1,82 @@
// (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 { Text } from "@docspace/shared/components/text";
import { TTranslation } from "@docspace/shared/types";
import RoundedArrowSvgUrl from "PUBLIC_DIR/images/rounded arrow.react.svg?url";
import CrossIconSvgUrl from "PUBLIC_DIR/images/cross.react.svg?url";
import { IconButton } from "@docspace/shared/components/icon-button";
import { StyledTableIndexMenu } from "./StyledIndexHeader";
export interface IndexMenuProps {
setIsIndexEditingMode: (mode: boolean) => void;
setReorderDialogVisible: (visible: boolean) => void;
t: TTranslation;
}
const IndexMenu = ({
setIsIndexEditingMode,
t,
setReorderDialogVisible,
}: IndexMenuProps) => {
return (
<StyledTableIndexMenu>
<div className="table-header_index-container">
<Text fontSize="14px" lineHeight="16px" fontWeight={600}>
{t("Common:SortingIndex")}
</Text>
<div className="table-header_index-separator" />
<div
className="table-header_reorder-container"
onClick={() => setReorderDialogVisible(true)}
>
<IconButton
className="table-header_reorder-icon"
size={16}
onClick={() => {}}
iconName={RoundedArrowSvgUrl}
isFill
isClickable={false}
/>
<Text fontSize="12px" lineHeight="16px" fontWeight={600}>
{t("Files:Reorder")}
</Text>
</div>
</div>
<IconButton
className="table-header_cross-icon"
size={16}
onClick={() => setIsIndexEditingMode(false)}
iconName={CrossIconSvgUrl}
isFill
isClickable={false}
/>
</StyledTableIndexMenu>
);
};
export default IndexMenu;

View File

@ -39,6 +39,7 @@ const SelectionArea = (props) => {
foldersLength,
filesLength,
isInfoPanelVisible,
isIndexEditingMode,
} = props;
const [countTilesInRow, setCountTilesInRow] = useState(getCountTilesInRow());
@ -87,7 +88,7 @@ const SelectionArea = (props) => {
},
];
return isMobile || dragging ? (
return isMobile || dragging || isIndexEditingMode ? (
<></>
) : (
<SelectionAreaComponent
@ -107,28 +108,32 @@ const SelectionArea = (props) => {
);
};
export default inject(({ filesStore, treeFoldersStore, infoPanelStore }) => {
const {
dragging,
viewAs,
setSelections,
getCountTilesInRow,
folders,
files,
} = filesStore;
const { isRoomsFolder, isArchiveFolder } = treeFoldersStore;
const { isVisible: isInfoPanelVisible } = infoPanelStore;
export default inject(
({ filesStore, treeFoldersStore, infoPanelStore, indexingStore }) => {
const {
dragging,
viewAs,
setSelections,
getCountTilesInRow,
folders,
files,
} = filesStore;
const { isRoomsFolder, isArchiveFolder } = treeFoldersStore;
const { isVisible: isInfoPanelVisible } = infoPanelStore;
const { isIndexEditingMode } = indexingStore;
const isRooms = isRoomsFolder || isArchiveFolder;
const isRooms = isRoomsFolder || isArchiveFolder;
return {
dragging,
viewAs,
setSelections,
getCountTilesInRow,
isRooms,
foldersLength: folders.length,
filesLength: files.length,
isInfoPanelVisible,
};
})(observer(SelectionArea));
return {
dragging,
viewAs,
setSelections,
getCountTilesInRow,
isRooms,
foldersLength: folders.length,
filesLength: files.length,
isInfoPanelVisible,
isIndexEditingMode,
};
},
)(observer(SelectionArea));

View File

@ -115,6 +115,7 @@ const PureHome = (props) => {
isPrivacyFolder,
isRecycleBinFolder,
isErrorRoomNotAvailable,
isIndexEditingMode,
primaryProgressDataPercent,
primaryProgressDataIcon,
@ -351,6 +352,7 @@ const PureHome = (props) => {
sectionProps.secondaryProgressBarIcon = secondaryProgressDataStoreIcon;
sectionProps.showSecondaryButtonAlert = secondaryProgressDataStoreAlert;
sectionProps.getContextModel = getContextModel;
sectionProps.isIndexEditingMode = isIndexEditingMode;
return (
<>
@ -439,6 +441,7 @@ export default inject(
currentTariffStatusStore,
settingsStore,
contextOptionsStore,
indexingStore,
}) => {
const { setSelectedFolder, security: folderSecurity } = selectedFolderStore;
const {
@ -605,6 +608,7 @@ export default inject(
isErrorRoomNotAvailable,
isRoomsFolder,
isArchiveFolder,
isIndexEditingMode: indexingStore.isIndexEditingMode,
disableDrag,

View File

@ -56,6 +56,7 @@ import UnmuteReactSvgUrl from "PUBLIC_DIR/images/unmute.react.svg?url";
import MuteReactSvgUrl from "PUBLIC_DIR/images/icons/16/mute.react.svg?url";
import ShareReactSvgUrl from "PUBLIC_DIR/images/share.react.svg?url";
import InvitationLinkReactSvgUrl from "PUBLIC_DIR/images/invitation.link.react.svg?url";
import EditIndexReactSvgUrl from "PUBLIC_DIR/images/edit.index.react.svg?url";
import TabletLinkReactSvgUrl from "PUBLIC_DIR/images/tablet-link.react.svg?url";
import MailReactSvgUrl from "PUBLIC_DIR/images/mail.react.svg?url";
import RoomArchiveSvgUrl from "PUBLIC_DIR/images/room.archive.svg?url";
@ -133,6 +134,7 @@ class ContextOptionsStore {
infoPanelStore;
currentTariffStatusStore;
userStore;
indexingStore;
clientLoadingStore;
linksIsLoading = false;
@ -154,6 +156,7 @@ class ContextOptionsStore {
infoPanelStore,
currentTariffStatusStore,
userStore,
indexingStore,
clientLoadingStore,
) {
makeAutoObservable(this);
@ -173,6 +176,7 @@ class ContextOptionsStore {
this.infoPanelStore = infoPanelStore;
this.currentTariffStatusStore = currentTariffStatusStore;
this.userStore = userStore;
this.indexingStore = indexingStore;
this.clientLoadingStore = clientLoadingStore;
}
@ -918,6 +922,10 @@ class ContextOptionsStore {
this.filesActionsStore.exportRoomIndex(t, roomId);
};
onEditIndex = () => {
this.indexingStore.setIsIndexEditingMode(true);
};
onClickRemoveFromRecent = (item) => {
this.filesActionsStore.removeFilesFromRecent([item.id]);
};
@ -1406,6 +1414,17 @@ class ContextOptionsStore {
const isArchive = item.rootFolderType === FolderType.Archive;
const isShared = shared || navigationPath.findIndex((r) => r.shared) > -1;
const { isIndexing } = this.indexingStore;
const indexOptions = {
id: "option_edit-index",
key: "edit-index",
label: t("Common:EditIndex"),
icon: EditIndexReactSvgUrl,
onClick: () => this.onEditIndex(),
disabled: !isIndexing && item.security?.Edit,
};
const optionsModel = [
{
id: "option_select",
@ -1716,6 +1735,7 @@ class ContextOptionsStore {
onClick: this.onRestoreAction,
disabled: false,
},
indexOptions,
{
id: "option_rename",
key: "rename",
@ -1797,7 +1817,6 @@ class ContextOptionsStore {
disabled: !this.treeFoldersStore.isRecentTab,
},
];
const options = this.filterModel(optionsModel, contextOptions);
const pluginItems = this.onLoadPlugins(item);

View File

@ -43,6 +43,7 @@ class DialogsStore {
ownerPanelVisible = false;
moveToPanelVisible = false;
restorePanelVisible = false;
reorderDialogVisible = false;
copyPanelVisible = false;
deleteThirdPartyDialogVisible = false;
connectDialogVisible = false;
@ -519,6 +520,10 @@ class DialogsStore {
this.pdfFormEditVisible = visible;
this.pdfFormEditData = data;
};
setReorderDialogVisible = (visible) => {
this.reorderDialogVisible = visible;
};
}
export default DialogsStore;

View File

@ -54,6 +54,8 @@ import {
moveToFolder,
getFolder,
deleteFilesFromRecent,
changeIndex,
reorder,
} from "@docspace/shared/api/files";
import {
ConflictResolveType,
@ -64,6 +66,7 @@ import {
FolderType,
RoomsType,
ShareAccessRights,
VDRIndexingAction,
} from "@docspace/shared/enums";
import { makeAutoObservable } from "mobx";
@ -106,6 +109,7 @@ class FilesActionStore {
publicRoomStore;
infoPanelStore;
peopleStore;
indexingStore;
userStore = null;
currentTariffStatusStore = null;
currentQuotaStore = null;
@ -134,6 +138,7 @@ class FilesActionStore {
currentTariffStatusStore,
peopleStore,
currentQuotaStore,
indexingStore,
) {
makeAutoObservable(this);
this.settingsStore = settingsStore;
@ -153,6 +158,7 @@ class FilesActionStore {
this.currentTariffStatusStore = currentTariffStatusStore;
this.peopleStore = peopleStore;
this.currentQuotaStore = currentQuotaStore;
this.indexingStore = indexingStore;
}
setIsBulkDownload = (isBulkDownload) => {
@ -2757,6 +2763,71 @@ class FilesActionStore {
.itemOperationToFolder(operationData)
.catch((error) => toastr.error(error));
};
changeIndex = async (action, item, t) => {
const { filesList } = this.filesStore;
const index = filesList.findIndex(
(elem) => elem.id === item?.id && elem.fileExst === item?.fileExst,
);
if (
(action === VDRIndexingAction.HigherIndex && index === 0) ||
(action === VDRIndexingAction.LowerIndex &&
index === filesList.length - 1)
)
return;
const { bufferSelection } = this.filesStore;
let selection = this.filesStore.selection.length
? this.filesStore.selection
: [bufferSelection];
const { id } = this.selectedFolderStore;
const { setUpdateItems } = this.indexingStore;
let replaceable;
let current = item;
switch (action) {
case VDRIndexingAction.HigherIndex:
replaceable = filesList[index - 1];
break;
case VDRIndexingAction.LowerIndex:
replaceable = filesList[index + 1];
break;
default:
current = selection[0];
replaceable = item;
break;
}
if (!replaceable) return;
try {
await changeIndex(current?.id, replaceable.order, current?.isFolder);
const items = [current, replaceable];
setUpdateItems(items);
const operationId = uniqueid("operation_");
this.updateCurrentFolder(null, [id], true, operationId);
} catch (e) {
toastr.error(t("Files:ErrorChangeIndex"));
}
};
reorder = async (id, t) => {
try {
const operationId = uniqueid("operation_");
await reorder(id);
this.updateCurrentFolder(null, [id], true, operationId);
} catch (e) {
toastr.error(t("Files:ErrorChangeIndex"));
}
};
checkExportRoomIndexProgress = async () => {
return await new Promise((resolve, reject) => {

View File

@ -96,6 +96,7 @@ class FilesStore {
publicRoomStore;
settingsStore;
currentQuotaStore;
indexingStore;
pluginStore;
@ -200,6 +201,7 @@ class FilesStore {
userStore,
currentTariffStatusStore,
settingsStore,
indexingStore,
) {
const pathname = window.location.pathname.toLowerCase();
this.isEditor = pathname.indexOf("doceditor") !== -1;
@ -218,6 +220,7 @@ class FilesStore {
this.infoPanelStore = infoPanelStore;
this.currentTariffStatusStore = currentTariffStatusStore;
this.settingsStore = settingsStore;
this.indexingStore = indexingStore;
this.roomsController = new AbortController();
this.filesController = new AbortController();
@ -1440,7 +1443,6 @@ class FilesStore {
`${url}?${RoomsFilter.getDefault().toUrlParams()}`,
);
}
this.setIsErrorRoomNotAvailable(false);
this.setIsLoadedFetchFiles(false);
@ -1450,7 +1452,6 @@ class FilesStore {
if (filterStorageItem && !filter) {
const splitFilter = filterStorageItem.split(",");
filterData.sortBy = splitFilter[0];
filterData.pageCount = +splitFilter[1];
filterData.sortOrder = splitFilter[2];
@ -1501,12 +1502,17 @@ class FilesStore {
await this.publicRoomStore.getExternalLinks(data.current.id);
}
if (data.current.indexing || data.current.order) {
this.indexingStore.setIsIndexing(true);
} else if (data.current.rootFolderId !== FolderType.COMMON) {
this.indexingStore.setIsIndexing(false);
}
if (newTotal > 0) {
const lastPage = filterData.getLastPage();
if (filterData.page > lastPage) {
filterData.page = lastPage;
return this.fetchFiles(
folderId,
filterData,
@ -1748,6 +1754,9 @@ class FilesStore {
withFilterLocalStorage = false,
) => {
const { setSelectedNode, roomsFolderId } = this.treeFoldersStore;
const { setIsIndexing, isIndexing } = this.indexingStore;
if (isIndexing) setIsIndexing(false);
if (this.clientLoadingStore.isLoading) {
this.abortAllFetch();
@ -2093,6 +2102,7 @@ class FilesStore {
"copy",
"restore",
"rename",
"edit-index",
"separator2",
// "unsubscribe",
"delete",
@ -2501,6 +2511,7 @@ class FilesStore {
"copy-to",
"mark-read",
"restore",
"edit-index",
"rename",
// "change-thirdparty-info",
"separator2",
@ -3243,6 +3254,7 @@ class FilesStore {
usedSpace,
isCustomQuota,
providerId,
order,
startFilling,
draftLocation,
expired,
@ -3419,6 +3431,7 @@ class FilesStore {
usedSpace,
isCustomQuota,
providerId,
order,
startFilling,
draftLocation,
expired,
@ -3429,6 +3442,22 @@ class FilesStore {
//return [...this.folders, ...this.files];
const newFolders = [...this.folders];
const orderItems = [...this.folders, ...this.files].filter((x) => x.order);
if (orderItems.length > 0) {
orderItems.sort((a, b) => {
if (a.order.includes(".")) {
return (
Number(a.order.split(".").at(-1)) -
Number(b.order.split(".").at(-1))
);
}
return Number(a.order) - Number(b.order);
});
return this.getFilesListItems(orderItems);
}
newFolders.sort((a, b) => {
const firstValue = a.roomType ? 1 : 0;
@ -4050,6 +4079,15 @@ class FilesStore {
return this.filter.total;
}
get indexColumnSize() {
if (!this.indexingStore.isIndexing) return;
let minWidth = 36;
const lastFile = this.filesList[this.filesList.length - 1];
return minWidth + lastFile?.order?.length * 3;
}
get hasMoreFiles() {
const { isRoomsFolder, isArchiveFolder } = this.treeFoldersStore;

View File

@ -0,0 +1,85 @@
// (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 { makeAutoObservable } from "mobx";
import InfoPanelStore from "SRC_DIR/store/InfoPanelStore";
class IndexingStore {
infoPanelStore;
isIndexEditingMode: boolean = false;
isIndexing: boolean = false;
updateSelection: any[] = [];
constructor(infoPanelStore: InfoPanelStore) {
this.infoPanelStore = infoPanelStore;
makeAutoObservable(this);
}
setIsIndexing = (indexing: boolean) => {
// turn off the mode if we are no longer in indexed folders
const { setIsVisible } = this.infoPanelStore;
if (!indexing && this.isIndexEditingMode) this.setIsIndexEditingMode(false);
if (!indexing) setIsVisible(false);
this.isIndexing = indexing;
};
setUpdateSelection = (selection: any[]) => {
this.updateSelection = selection;
};
setUpdateItems = (items: any) => {
const newSelection = [...this.updateSelection];
// eslint-disable-next-line no-restricted-syntax
for (const item of items) {
const exist = this.updateSelection.find(
(selectionItem) =>
selectionItem.id === item.id &&
selectionItem.fileExst === item.fileExst,
);
// eslint-disable-next-line no-continue
if (exist) continue;
newSelection.push(item);
}
this.setUpdateSelection(newSelection);
};
setIsIndexEditingMode = (mode: boolean) => {
if (!mode) {
this.setUpdateSelection([]);
}
const { setIsVisible } = this.infoPanelStore;
setIsVisible(false);
this.isIndexEditingMode = mode;
};
}
export default IndexingStore;

View File

@ -149,6 +149,8 @@ class SelectedFolderStore {
lifetime: TRoomLifetime | null = null;
indexing = false;
constructor(settingsStore: SettingsStore) {
makeAutoObservable(this);
this.settingsStore = settingsStore;

View File

@ -26,6 +26,7 @@
import { makeAutoObservable } from "mobx";
import { TableVersions } from "SRC_DIR/helpers/constants";
import { RoomsType } from "@docspace/shared/enums";
const TABLE_COLUMNS = `filesTableColumns_ver-${TableVersions.Files}`;
const TABLE_ACCOUNTS_PEOPLE_COLUMNS = `peopleTableColumns_ver-${TableVersions.People}`;
@ -34,11 +35,13 @@ const TABLE_ACCOUNTS_INSIDE_GROUP_COLUMNS = `insideGroupTableColumns_ver-${Table
const TABLE_ROOMS_COLUMNS = `roomsTableColumns_ver-${TableVersions.Rooms}`;
const TABLE_TRASH_COLUMNS = `trashTableColumns_ver-${TableVersions.Trash}`;
const TABLE_RECENT_COLUMNS = `recentTableColumns_ver-${TableVersions.Recent}`;
const TABLE_VDR_INDEXING_COLUMNS = `vdrIndexingColumns_ver-${TableVersions.Rooms}`;
const COLUMNS_SIZE = `filesColumnsSize_ver-${TableVersions.Files}`;
const COLUMNS_ROOMS_SIZE = `roomsColumnsSize_ver-${TableVersions.Rooms}`;
const COLUMNS_TRASH_SIZE = `trashColumnsSize_ver-${TableVersions.Trash}`;
const COLUMNS_RECENT_SIZE = `recentColumnsSize_ver-${TableVersions.Recent}`;
const COLUMNS_VDR_INDEXING_SIZE = `vdrIndexingColumnsSize_ver-${TableVersions.Rooms}`;
const COLUMNS_SIZE_INFO_PANEL = `filesColumnsSizeInfoPanel_ver-${TableVersions.Files}`;
const COLUMNS_ROOMS_SIZE_INFO_PANEL = `roomsColumnsSizeInfoPanel_ver-${TableVersions.Rooms}`;
@ -50,6 +53,7 @@ class TableStore {
treeFoldersStore;
userStore;
settingsStore;
selectedFolderStore;
roomColumnNameIsEnabled = true; // always true
roomColumnTypeIsEnabled = false;
@ -66,6 +70,7 @@ class TableStore {
createdColumnIsEnabled = true;
modifiedColumnIsEnabled = true;
sizeColumnIsEnabled = true;
indexColumnIsEnabled = true;
typeColumnIsEnabled = true;
quickButtonsColumnIsEnabled = true;
lastOpenedColumnIsEnabled = true;
@ -86,13 +91,20 @@ class TableStore {
groupAccountsInsideGroupColumnIsEnabled = true;
emailAccountsInsideGroupColumnIsEnabled = true;
constructor(authStore, treeFoldersStore, userStore, settingsStore) {
constructor(
authStore,
treeFoldersStore,
userStore,
settingsStore,
indexingStore,
) {
makeAutoObservable(this);
this.authStore = authStore;
this.treeFoldersStore = treeFoldersStore;
this.userStore = userStore;
this.settingsStore = settingsStore;
this.indexingStore = indexingStore;
}
setRoomColumnType = (enable) => {
@ -139,6 +151,10 @@ class TableStore {
this.sizeColumnIsEnabled = enable;
};
setIndexColumn = (enable) => {
this.indexColumnIsEnabled = enable;
};
setTypeColumn = (enable) => {
this.typeColumnIsEnabled = enable;
};
@ -240,6 +256,7 @@ class TableStore {
this.setAuthorColumn(splitColumns.includes("Author"));
this.setCreatedColumn(splitColumns.includes("Created"));
this.setSizeColumn(splitColumns.includes("Size"));
this.setIndexColumn(splitColumns.includes("Index"));
this.setTypeColumn(splitColumns.includes("Type"));
this.setQuickButtonsColumn(splitColumns.includes("QuickButtons"));
this.setLastOpenedColumn(splitColumns.includes("LastOpened"));
@ -292,6 +309,10 @@ class TableStore {
this.setErasureColumn(!this.erasureColumnIsEnabled);
return;
case "Index":
this.setIndexColumn(!this.indexColumnIsEnabled);
return;
case "Size":
this.setSizeColumn(!this.sizeColumnIsEnabled);
return;
@ -403,6 +424,7 @@ class TableStore {
getIsAccountsInsideGroup,
} = this.treeFoldersStore;
const { isIndexing } = this.indexingStore;
const isRooms = isRoomsFolder || isArchiveFolder;
const userId = this.userStore.user?.id;
const isFrame = this.settingsStore.isFrame;
@ -419,7 +441,9 @@ class TableStore {
? `${TABLE_TRASH_COLUMNS}=${userId}`
: isRecentTab
? `${TABLE_RECENT_COLUMNS}=${userId}`
: `${TABLE_COLUMNS}=${userId}`;
: isIndexing
? `${TABLE_VDR_INDEXING_COLUMNS}=${userId}`
: `${TABLE_COLUMNS}=${userId}`;
return isFrame ? `SDK_${tableStorageName}` : tableStorageName;
}
@ -428,6 +452,7 @@ class TableStore {
get columnStorageName() {
const { isRoomsFolder, isArchiveFolder, isTrashFolder, isRecentTab } =
this.treeFoldersStore;
const { isIndexing } = this.indexingStore;
const isRooms = isRoomsFolder || isArchiveFolder;
const userId = this.userStore.user?.id;
const isFrame = this.settingsStore.isFrame;
@ -438,7 +463,9 @@ class TableStore {
? `${COLUMNS_TRASH_SIZE}=${userId}`
: isRecentTab
? `${COLUMNS_RECENT_SIZE}=${userId}`
: `${COLUMNS_SIZE}=${userId}`;
: isIndexing
? `${COLUMNS_VDR_INDEXING_SIZE}=${userId}`
: `${COLUMNS_SIZE}=${userId}`;
return isFrame ? `SDK_${columnStorageName}` : columnStorageName;
}

View File

@ -80,6 +80,7 @@ import ImportAccountsStore from "./ImportAccountsStore";
import PluginStore from "./PluginStore";
import InfoPanelStore from "./InfoPanelStore";
import CampaignsStore from "./CampaignsStore";
import IndexingStore from "./IndexingStore";
const selectedFolderStore = new SelectedFolderStore(settingsStore);
@ -115,6 +116,7 @@ const clientLoadingStore = new ClientLoadingStore();
const publicRoomStore = new PublicRoomStore(clientLoadingStore);
const infoPanelStore = new InfoPanelStore(userStore);
const indexingStore = new IndexingStore(infoPanelStore);
const treeFoldersStore = new TreeFoldersStore(
selectedFolderStore,
@ -151,6 +153,7 @@ const filesStore = new FilesStore(
userStore,
currentTariffStatusStore,
settingsStore,
indexingStore,
);
const mediaViewerDataStore = new MediaViewerDataStore(
@ -214,6 +217,7 @@ const filesActionsStore = new FilesActionsStore(
currentTariffStatusStore,
peopleStore,
currentQuotaStore,
indexingStore,
);
const contextOptionsStore = new ContextOptionsStore(
@ -233,6 +237,7 @@ const contextOptionsStore = new ContextOptionsStore(
infoPanelStore,
currentTariffStatusStore,
userStore,
indexingStore,
clientLoadingStore,
);
@ -264,6 +269,7 @@ const tableStore = new TableStore(
treeFoldersStore,
userStore,
settingsStore,
indexingStore,
);
infoPanelStore.filesSettingsStore = filesSettingsStore;
@ -351,6 +357,7 @@ const store = {
pluginStore,
storageManagement,
campaignsStore,
indexingStore,
};
export default store;

View File

@ -1435,6 +1435,26 @@ export async function startFilling(fileId: string | number): Promise<void> {
await request(options);
}
export async function changeIndex(
id: number,
order: number,
isFolder: boolean,
) {
const url = isFolder ? `/files/folder/${id}/order` : `/files/${id}/order`;
return request({
method: "put",
url,
data: { order },
});
}
export async function reorder(id: number) {
return request({
method: "put",
url: `/files/rooms/${id}/reorder`,
});
}
export async function checkIsPDFForm(fileId: string | number) {
return request({
method: "get",

View File

@ -39,4 +39,5 @@ export const enum ThemeId {
IndicatorLoader = "indicatorLoader",
Progress = "progress",
SubmenuText = "submenuText",
IndexIconButton = "indexIconButton",
}

View File

@ -46,6 +46,7 @@ import LoadingButton from "./styled-components/loadingButton";
import ProgressColorTheme from "./styled-components/progress";
import VersionBadgeTheme from "./styled-components/versionBadge";
import SubmenuTextTheme from "./styled-components/submenuText";
import StyledIndexWrapper from "./sub-components/StyledIndexWrapper";
const ColorTheme = forwardRef<
HTMLDivElement,
@ -76,6 +77,20 @@ const ColorTheme = forwardRef<
/>
);
}
case ThemeId.IndexIconButton: {
return (
<StyledIndexWrapper
$currentColorScheme={currentColorScheme}
onClick={props.onClick}
>
<IconButtonTheme
{...props}
themeId={themeId}
$currentColorScheme={currentColorScheme}
/>
</StyledIndexWrapper>
);
}
case ThemeId.IconButtonMute: {
return (

View File

@ -77,6 +77,10 @@ export interface IndicatorFilterButtonColorTheme
themeId: ThemeId.IndicatorFilterButton;
}
export interface IndexIconButton extends DefaultColorThemeProps {
themeId: ThemeId.IndexIconButton;
}
export interface IndicatorLoaderColorTheme extends DefaultColorThemeProps {
themeId: ThemeId.IndicatorLoader;
}
@ -132,4 +136,5 @@ export type ColorThemeProps =
| ProgressColorTheme
| VersionBadgeTheme
| LinkColorTheme
| IndexIconButton
| SubmenuTextTheme;

View File

@ -0,0 +1,73 @@
// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import styled from "styled-components";
import { Base, TColorScheme } from "../../../themes";
const StyledIndexWrapper = styled.div<{
$currentColorScheme: TColorScheme | undefined;
}>`
width: 24px;
height: 24px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 3px;
.index-up-icon {
transform: rotate(-90deg);
}
.index-down-icon {
transform: rotate(90deg);
}
&:hover {
cursor: pointer;
background: ${(props) =>
props.theme.filesSection.tableView.row.indexBackgroundButtonHover};
svg {
cursor: pointer;
path {
fill: ${(props) =>
props.theme.filesSection.tableView.row
.indexArrowButtonHover} !important;
}
circle {
stroke: ${(props) =>
props.theme.filesSection.tableView.row
.indexArrowButtonHover} !important;
}
}
}
`;
StyledIndexWrapper.defaultProps = { theme: Base };
export default StyledIndexWrapper;

View File

@ -233,6 +233,9 @@ export interface FilterProps {
isPeopleAccounts: boolean;
isGroupsAccounts: boolean;
isInsideGroup: boolean;
isIndexing: boolean;
isIndexEditingMode: boolean;
filterTitle: string;
sortByTitle: string;

View File

@ -71,6 +71,9 @@ const FilterInput = React.memo(
isPeopleAccounts,
isGroupsAccounts,
isInsideGroup,
isIndexing,
isIndexEditingMode,
filterTitle,
sortByTitle,
@ -208,43 +211,49 @@ const FilterInput = React.memo(
onClearSearch={onClearSearch}
id="filter_search-input"
size={InputSize.base}
isDisabled={isIndexEditingMode}
onFocus={onInputFocus}
/>
<FilterButton
id="filter-button"
onFilter={onFilter}
getFilterData={getFilterData}
selectedFilterValue={selectedFilterValue}
filterHeader={filterHeader}
selectorLabel={selectorLabel}
isRooms={isRooms}
isAccounts={isAccounts}
isPeopleAccounts={isPeopleAccounts}
isGroupsAccounts={isGroupsAccounts}
isInsideGroup={isInsideGroup}
title={filterTitle}
userId={userId}
disableThirdParty={disableThirdParty}
/>
<SortButton
id="sort-by-button"
onSort={onSort}
getSortData={getSortData}
getSelectedSortData={getSelectedSortData}
view={view}
viewAs={viewAs === "table" ? "row" : viewAs}
viewSettings={viewSettings}
onChangeViewAs={onChangeViewAs}
onSortButtonClick={onSortButtonClick}
viewSelectorVisible={
viewSettings &&
viewSelectorVisible &&
currentDeviceType !== DeviceType.desktop
}
title={sortByTitle}
/>
{!isIndexEditingMode && (
<FilterButton
id="filter-button"
onFilter={onFilter}
getFilterData={getFilterData}
selectedFilterValue={selectedFilterValue}
filterHeader={filterHeader}
selectorLabel={selectorLabel}
isRooms={isRooms}
isAccounts={isAccounts}
isPeopleAccounts={isPeopleAccounts}
isGroupsAccounts={isGroupsAccounts}
isInsideGroup={isInsideGroup}
title={filterTitle}
userId={userId}
disableThirdParty={disableThirdParty}
/>
)}
{!isIndexing && (
<SortButton
id="sort-by-button"
onSort={onSort}
getSortData={getSortData}
getSelectedSortData={getSelectedSortData}
view={view}
viewAs={viewAs === "table" ? "row" : viewAs}
viewSettings={viewSettings}
onChangeViewAs={onChangeViewAs}
onSortButtonClick={onSortButtonClick}
viewSelectorVisible={
viewSettings &&
viewSelectorVisible &&
currentDeviceType !== DeviceType.desktop
}
title={sortByTitle}
/>
)}
{viewSettings &&
!isIndexing &&
currentDeviceType === DeviceType.desktop &&
viewSelectorVisible && (
<ViewSelector

View File

@ -14,6 +14,8 @@ export const getRoomTypeTitleTranslation = (
// return t("Common:ReviewRoomTitle");
// case RoomsType.ReadOnlyRoom:
// return t("Common:ViewOnlyRoomTitle");
case RoomsType.VirtualDataRoom:
return t("Common:VirtualDataRoom");
case RoomsType.CustomRoom:
return t("Common:CustomRoomTitle");
case RoomsType.PublicRoom:
@ -40,6 +42,8 @@ export const getRoomTypeDescriptionTranslation = (
// return t("Common:ReviewRoomDescription");
// case RoomsType.ReadOnlyRoom:
// return t("Common:ViewOnlyRoomDescription");
case RoomsType.VirtualDataRoom:
return t("Common:VirtualDataRoomDescription");
case RoomsType.CustomRoom:
return t("Common:CustomRoomDescription");
case RoomsType.PublicRoom:

View File

@ -25,11 +25,13 @@
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import React, { useRef } from "react";
import ArrowReactSvgUrl from "PUBLIC_DIR/images/arrow2.react.svg?url";
import { isMobile } from "react-device-detect"; // TODO: isDesktop=true for IOS(Firefox & Safari)
import { VDRIndexingAction } from "../../enums";
import { isMobile as isMobileUtils } from "../../utils/device";
import { Checkbox } from "../checkbox";
import { ColorTheme, ThemeId } from "../color-theme";
import {
ContextMenuButton,
ContextMenuButtonDisplayType,
@ -58,6 +60,7 @@ const Row = (props: RowProps) => {
onSelect,
onRowClick,
onContextClick,
onChangeIndex,
getContextModel,
isRoom,
@ -71,6 +74,7 @@ const Row = (props: RowProps) => {
className,
badgeUrl,
isDisabled,
isIndexEditingMode,
} = props;
const cm = useRef<null | {
@ -143,6 +147,11 @@ const Row = (props: RowProps) => {
onSelect?.(true, data);
};
const changeIndex = (e, action) => {
e.stopPropagation();
onChangeIndex(action);
};
return (
<StyledRow
ref={row}
@ -213,30 +222,51 @@ const Row = (props: RowProps) => {
{renderContentElement && (
<StyledContentElement>{contentElement}</StyledContentElement>
)}
{renderContext ? (
<ContextMenuButton
isFill
className="expandButton"
getData={getOptions}
directionX="right"
displayType={ContextMenuButtonDisplayType.toggle}
onClick={onContextMenu}
title={contextTitle}
/>
{isIndexEditingMode ? (
<>
<ColorTheme
themeId={ThemeId.IndexIconButton}
iconName={ArrowReactSvgUrl}
className="index-up-icon"
size="small"
onClick={(e) => changeIndex(e, VDRIndexingAction.HigherIndex)}
/>
<ColorTheme
themeId={ThemeId.IndexIconButton}
iconName={ArrowReactSvgUrl}
className="index-down-icon"
size="small"
onClick={(e) => changeIndex(e, VDRIndexingAction.LowerIndex)}
/>
</>
) : (
<div className="expandButton"> </div>
<>
{renderContext ? (
<ContextMenuButton
isFill
className="expandButton"
getData={getOptions}
directionX="right"
displayType={ContextMenuButtonDisplayType.toggle}
onClick={onContextMenu}
title={contextTitle}
/>
) : (
<div className="expandButton"> </div>
)}
<ContextMenu
getContextModel={getContextModel}
model={contextData.contextOptions || []}
ref={cm}
header={contextMenuHeader}
withBackdrop={isMobileUtils()}
onHide={rowContextClose}
isRoom={isRoom}
isArchive={isArchive}
badgeUrl={badgeUrl}
/>
</>
)}
<ContextMenu
getContextModel={getContextModel}
model={contextData.contextOptions || []}
ref={cm}
header={contextMenuHeader}
withBackdrop={isMobileUtils()}
onHide={rowContextClose}
isRoom={isRoom}
isArchive={isArchive}
badgeUrl={badgeUrl}
/>
</StyledOptionButton>
</StyledRow>
);

View File

@ -69,6 +69,7 @@ export interface RowProps {
mode?: TMode;
/** Removes the borders */
withoutBorder?: boolean;
isIndexEditingMode: boolean;
isRoom?: boolean;
contextTitle?: string;
badgesComponent?: React.ReactNode;

View File

@ -66,6 +66,7 @@ export interface SectionBodyProps {
settingsStudio: boolean;
isFormGallery?: boolean;
isDesktop?: boolean;
isIndexEditingMode?: boolean;
currentDeviceType?: DeviceType;
getContextModel?: () => ContextMenuModel[];
}
@ -144,4 +145,5 @@ export interface SectionProps {
anotherDialogOpen?: boolean;
isDesktop?: boolean;
getContextModel?: () => ContextMenuModel[];
isIndexEditingMode?: boolean;
}

View File

@ -117,6 +117,7 @@ const Section = (props: SectionProps) => {
canDisplay,
anotherDialogOpen,
getContextModel,
isIndexEditingMode,
} = props;
const [sectionSize, setSectionSize] = React.useState<{
@ -243,6 +244,7 @@ const Section = (props: SectionProps) => {
isFormGallery={isFormGallery}
currentDeviceType={currentDeviceType}
getContextModel={getContextModel}
isIndexEditingMode={isIndexEditingMode}
>
{isSectionHeaderAvailable &&
currentDeviceType === DeviceType.mobile && (

View File

@ -51,6 +51,7 @@ const SectionBody = React.memo(
isDesktop,
settingsStudio = false,
getContextModel,
isIndexEditingMode,
}: SectionBodyProps) => {
const focusRef = React.useRef<HTMLDivElement | null>(null);
const cmRef = React.useRef<null | {
@ -65,6 +66,7 @@ const SectionBody = React.memo(
const onContextMenu = React.useCallback(
(e: MouseEvent | React.MouseEvent<Element, MouseEvent>) => {
if (isIndexEditingMode) return;
const bodyElem = document.getElementsByClassName(
"section-body",
)[0] as HTMLDivElement;
@ -89,7 +91,7 @@ const SectionBody = React.memo(
setIsOpen(!isOpen);
}
},
[getContextModel, isOpen],
[getContextModel, isOpen, isIndexEditingMode],
);
const onHide = () => {

View File

@ -59,6 +59,11 @@ const StyledTableContainer = styled.div<{ useReactWindow?: boolean }>`
min-width: 10%;
}
.indexing-separator {
background-color: ${(props) =>
props.theme.tableContainer.indexingSeparator};
}
.resize-handle {
display: block;
cursor: ew-resize;
@ -443,7 +448,10 @@ const StyledTableBody = styled.div<{
}
`;
const StyledTableRow = styled.div<{ dragging?: boolean }>`
const StyledTableRow = styled.div<{
dragging?: boolean;
isIndexEditingMode?: boolean;
}>`
display: contents;
.table-container_header-checkbox {
@ -458,7 +466,7 @@ const StyledTableRow = styled.div<{ dragging?: boolean }>`
.droppable-hover {
background: ${(props) =>
props.dragging
props.dragging && !props.isIndexEditingMode
? `${props.theme.dragAndDrop.acceptBackground} !important`
: "none"};
}

View File

@ -50,6 +50,7 @@ export type TTableColumn = {
defaultSize?: number;
default?: boolean;
resizable?: boolean;
isShort?: boolean;
checkbox?: {
value: boolean;
isIndeterminate: boolean;
@ -108,6 +109,7 @@ export interface TableBodyProps {
useReactWindow: boolean;
onScroll: () => void;
infoPanelVisible: boolean;
isIndexEditingMode: boolean;
}
export interface TableRowProps {
@ -121,6 +123,7 @@ export interface TableRowProps {
title: string;
getContextModel: () => ContextMenuModel[];
badgeUrl: string;
isIndexEditingMode: boolean;
}
export interface TableCellProps {

View File

@ -44,6 +44,7 @@ const TableBodyPure = (props: TableBodyProps) => {
useReactWindow = true,
onScroll,
infoPanelVisible = false,
isIndexEditingMode = false,
} = props;
return useReactWindow ? (
@ -64,6 +65,7 @@ const TableBodyPure = (props: TableBodyProps) => {
itemSize={itemHeight}
onScroll={onScroll}
infoPanelVisible={infoPanelVisible}
isIndexEditingMode={isIndexEditingMode}
>
{children}
</InfiniteLoaderComponent>

View File

@ -43,7 +43,7 @@ import { checkingForUnfixedSize, getSubstring } from "./Table.utils";
const defaultMinColumnSize = 110;
const settingsSize = 24;
const minSizeFirstColumn = 210;
const minSizeFirstColumn = 75;
const handleOffset = 8;
class TableHeader extends React.Component<
@ -322,6 +322,7 @@ class TableHeader extends React.Component<
columns,
setHideColumns,
tableStorageName,
isIndexEditingMode,
} = this.props;
const enabledColumns = columns.filter((col) => col.enable);
@ -354,6 +355,9 @@ class TableHeader extends React.Component<
const defaultSize =
columns.find((col) => col.defaultSize && col.enable)?.defaultSize || 0;
const shortSize =
columns.find((col) => col.isShort && col.enable)?.minWidth || 0;
// TODO: Fixed columns size if something went wrong
if (storageSize) {
const splitStorage = storageSize.split(" ");
@ -409,6 +413,9 @@ class TableHeader extends React.Component<
const containerWidth = +container.clientWidth;
const indexColumnDifference = shortSize
? Number(tableContainer[0].split("px")[0]) - shortSize
: 0;
const defaultWidth = tableContainer
.map((column) => getSubstring(column))
.reduce((x, y) => x + y);
@ -417,7 +424,8 @@ class TableHeader extends React.Component<
.map((column) => getSubstring(column))
.reduce((x, y) => x + y);
const oldWidth = defaultWidth - defaultSize - settingsSize;
const oldWidth =
defaultWidth - defaultSize - settingsSize - indexColumnDifference;
const isDifferentWindowSize = infoPanelVisible
? Math.round(defaultInfoWidth) !== Math.round(containerWidth)
@ -494,13 +502,24 @@ class TableHeader extends React.Component<
}
if (hideColumnsConst) {
const shortColumnSize =
columns.find((col) => col.isShort && col.enable)?.minWidth || 0;
tableInfoPanelContainer.forEach((item, index) => {
const column = document.getElementById(`column_${index}`);
const isQuickButtonColumn =
Number(index) === tableInfoPanelContainer.length - 2;
if (isIndexEditingMode && isQuickButtonColumn) {
gridTemplateColumns.push("24px");
}
if (column?.dataset?.minWidth && column?.dataset?.default) {
gridTemplateColumns.push(
`${containerWidth - defaultSize - settingsSize}px`,
`${containerWidth - defaultSize - shortColumnSize - settingsSize}px`,
);
} else if (column?.dataset?.minWidth && column?.dataset?.shortColum) {
gridTemplateColumns.push(`${column?.dataset?.minWidth}px`);
} else if (
item === `${defaultSize}px` ||
item === `${settingsSize}px`
@ -665,6 +684,12 @@ class TableHeader extends React.Component<
+index === tableContainer.length - 1 ||
(column ? column.dataset.enable === "true" : item !== "0px");
const defaultColumnSize = column && column.dataset.defaultSize;
const shortColumSize =
column?.dataset?.shortColum && column.dataset.minWidth;
const isSettingColumn = Number(index) === tableContainer.length - 1;
const isQuickButtonColumn =
Number(index) === tableContainer.length - 2;
const isActiveNow = item === "0px" && enable;
if (isActiveNow && column) activeColumnIndex = index;
@ -684,6 +709,11 @@ class TableHeader extends React.Component<
getSubstring(gridTemplateColumns[+index - colIndex]) +
getSubstring(item)
}px`;
} else if (isSettingColumn) {
let newSettingsSize = isIndexEditingMode ? 75 : settingsSize;
gridTemplateColumns.push(`${newSettingsSize}px`);
} else if (shortColumSize) {
gridTemplateColumns.push(`${shortColumSize}px`);
} else if (item !== `${settingsSize}px`) {
const percent = (getSubstring(item) / oldWidth) * 100;
@ -898,6 +928,8 @@ class TableHeader extends React.Component<
for (const col of columns) {
if (col.default) {
str += `${wideColumnSize} `;
} else if (col.isShort) {
str += `${col.minWidth}px `;
} else
str += col.enable
? col.defaultSize

View File

@ -25,6 +25,8 @@
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import React, { useRef } from "react";
import ArrowReactSvgUrl from "PUBLIC_DIR/images/arrow2.react.svg?url";
import { VDRIndexingAction } from "../../enums";
import { ContextMenu } from "../context-menu";
import {
@ -36,6 +38,7 @@ import { StyledTableRow } from "./Table.styled";
import { TableRowProps } from "./Table.types";
import { TableCell } from "./sub-components/TableCell";
import { ColorTheme, ThemeId } from "../color-theme";
const TableRow = (props: TableRowProps) => {
const {
@ -49,6 +52,8 @@ const TableRow = (props: TableRowProps) => {
title,
getContextModel,
badgeUrl,
isIndexEditingMode,
onChangeIndex,
...rest
} = props;
@ -82,9 +87,15 @@ const TableRow = (props: TableRowProps) => {
return contextOptions;
};
const changeIndex = (e, action) => {
e.stopPropagation();
onChangeIndex(action);
};
return (
<StyledTableRow
onContextMenu={onContextMenu}
isIndexEditingMode={isIndexEditingMode}
className={`${className} table-container_row`}
{...rest}
>
@ -96,27 +107,48 @@ const TableRow = (props: TableRowProps) => {
forwardedRef={row}
className={`${selectionProp?.className} table-container_row-context-menu-wrapper`}
>
<ContextMenu
onHide={onHideContextMenu}
ref={cm}
model={contextOptions}
getContextModel={getContextModel}
withBackdrop
badgeUrl={badgeUrl}
/>
{renderContext ? (
<ContextMenuButton
isFill
className="expandButton"
getData={getOptions}
directionX="right"
displayType={ContextMenuButtonDisplayType.toggle}
onClick={onContextMenu}
onClose={onHideContextMenu}
title={title}
/>
{isIndexEditingMode ? (
<>
<ColorTheme
themeId={ThemeId.IndexIconButton}
iconName={ArrowReactSvgUrl}
className="index-up-icon"
size="small"
onClick={(e) => changeIndex(e, VDRIndexingAction.HigherIndex)}
/>
<ColorTheme
themeId={ThemeId.IndexIconButton}
iconName={ArrowReactSvgUrl}
className="index-down-icon"
size="small"
onClick={(e) => changeIndex(e, VDRIndexingAction.LowerIndex)}
/>
</>
) : (
<div className="expandButton"> </div>
<>
<ContextMenu
onHide={onHideContextMenu}
ref={cm}
model={contextOptions}
getContextModel={getContextModel}
withBackdrop
badgeUrl={badgeUrl}
/>
{renderContext ? (
<ContextMenuButton
isFill
className="expandButton"
getData={getOptions}
directionX="right"
displayType={ContextMenuButtonDisplayType.toggle}
onClick={onContextMenu}
onClose={onHideContextMenu}
title={title}
/>
) : (
<div className="expandButton"> </div>
)}
</>
)}
</TableCell>
</div>

View File

@ -55,6 +55,7 @@ const TableHeaderCell = ({
withTagRef,
default: isDefault,
checkbox,
isShort,
} = column;
const isActive = (sortBy && column.sortBy === sortBy) || active;
@ -82,6 +83,7 @@ const TableHeaderCell = ({
data-default={isDefault}
data-enable={enable}
data-min-width={minWidth}
data-short-colum={isShort}
data-default-size={defaultSize}
sortingVisible={sortingVisible}
ref={tagRef}
@ -120,6 +122,7 @@ const TableHeaderCell = ({
id={`column_${index}`}
data-enable={enable}
data-default={isDefault}
data-short-colum={isShort}
data-min-width={minWidth}
data-default-size={defaultSize}
sortingVisible={sortingVisible}

View File

@ -76,7 +76,7 @@ export const ROOMS_TYPE_TRANSLATIONS = Object.freeze({
5: "Files:CustomRooms",
6: "Files:PublicRoom",
8: "Files:VirtualDataRoom",
8: "Common:VirtualDataRoom",
});
export const ROOMS_PROVIDER_TYPE_NAME = Object.freeze({

View File

@ -567,6 +567,12 @@ export const enum RoomsStorageFilter {
thirdparty = 2,
}
export const enum VDRIndexingAction {
HigherIndex = "HigherIndex",
LowerIndex = "LowerIndex",
MoveIndex = "MoveIndex",
}
export const enum LDAPOperation {
SaveAndSync = "Save",
Sync = "Sync",

View File

@ -2229,6 +2229,8 @@ export const getBaseTheme = () => {
hoverBorderColor: grayMain,
tableCellBorder: `1px solid ${grayLightMid}`,
indexingSeparator: "#4781D1",
groupMenu: {
background: white,
borderBottom: "1px solid transparent",
@ -2298,6 +2300,11 @@ export const getBaseTheme = () => {
contextMenuWrapperDraggingHover: `linear-gradient(to left,rgb(239, 239, 178) 24px,${grayLightMid} 24px)`,
backgroundActive: `#F3F4F4`,
indexUpdate: `#F2F6FC`,
indexActive: `#E4ECF8`,
indexBackgroundButtonHover: `#BED3EF`,
indexArrowButtonHover: `#4781D1`,
borderImageCheckbox: `linear-gradient(to right, ${white} 24px, ${grayLightMid} 24px)`,
borderImageContextMenu: `linear-gradient(to left, ${white} 24px, ${grayLightMid} 24px)`,

View File

@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="1" width="16" height="2" rx="1" fill="#333333"/>
<rect y="5" width="13" height="2" rx="1" fill="#333333"/>
<rect y="9" width="10" height="2" rx="1" fill="#333333"/>
<rect y="13" width="7" height="2" rx="1" fill="#333333"/>
</svg>

After

Width:  |  Height:  |  Size: 335 B

View File

@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2269_37870)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.75736 3.75736C4.84424 2.67048 6.34287 2 8 2C11.3137 2 14 4.68629 14 8H16C16 3.58172 12.4183 0 8 0C5.79413 0 3.79519 0.894098 2.34903 2.33727L2.34682 2.33941L2.3235 2.36228C2.30404 2.38149 2.27684 2.40861 2.24255 2.44351C2.18243 2.50467 2.10045 2.58976 2 2.69795L2 1L0 1L0 5C0 5.55228 0.447715 6 1 6L5 6V4L3.52058 4C3.58201 3.93468 3.632 3.88303 3.66896 3.84542C3.696 3.81791 3.71606 3.79793 3.72851 3.78563L3.7414 3.77298L3.74296 3.77147L3.74328 3.77116L3.74331 3.77114L3.74343 3.77102L3.74997 3.76475L3.75736 3.75736ZM8 14C4.68629 14 2 11.3137 2 8H0C0 12.4183 3.58172 16 8 16C10.3903 16 12.535 14.9513 14 13.2915V15H16L16 11C16 10.4477 15.5523 10 15 10H11V12H12.4724C11.3728 13.2286 9.77618 14 8 14Z" fill="#333333"/>
</g>
<defs>
<clipPath id="clip0_2269_37870">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1022 B

View File

@ -143,6 +143,7 @@
"EditButton": "Edit",
"Editing": "Editing",
"Editor": "Editor",
"EditIndex": "Edit index",
"EditPDFForm": "Edit PDF form",
"Email": "Email",
"EmptyDescription": "The list of users previously invited to {{productName}} or separate rooms will appear here. You will be able to invite these users for collaboration at any time.",
@ -356,6 +357,7 @@
"ReloadPage": "Reload page",
"Remember": "Remember me",
"Remove": "Remove",
"Reorder": "Reorder",
"Rename": "Rename",
"RenewSubscription": "Renew subscription?",
"RepeatInvitation": "Repeat invitation",
@ -432,6 +434,7 @@
"SkipTitle": "Skip",
"SomethingWentWrong": "Something went wrong.",
"SortBy": "Sort by",
"SortingIndex": "Sorting Index",
"SpaceManagement": "Space Management",
"Spaces": "Spaces",
"SpacesInLocalPart": "Local part can't contain spaces",
@ -488,12 +491,27 @@
"Website": "Website",
"Yes": "Yes",
"Yesterday": "Yesterday",
"WED": "WED",
"Months": "Months",
"Years": "Years",
"MoveToTrash": "Move to trash",
"DeletePermanently": "Delete permanently",
"You": "You",
"VirtualDataRoom": "Virtual Data Room",
"VirtualDataRoomDescription": "Use VDR for advanced file security and transparency while filling and signing documents step-by-step. Set watermarks, automatically index and track all content, restrict downloading and copying."
"FormFilingRoomDescription": "Invite users to fill out a ready PDF form. Check the complete forms and analyze data automatically collected in a spreadsheet.",
"PublicRoomDescription": "Invite users via external links to view documents without registration. You can also embed this room into any web interface.",
"FillingFormsRoomTitle": "Filling forms room",
"CollaborationRoomTitle": "Collaboration room",
"CustomRoomDescription": "Apply your own settings to use this room for any custom purpose.",
"ReviewRoomTitle": "Review room",
"ViewOnlyRoomDescription": "Share any ready documents, reports, documentation, and other files for viewing.",
"ViewOnlyRoomTitle": "View-only room",
"ReviewRoomDescription": "Request a review or comments on the documents",
"CustomRoomTitle": "Custom room",
"CollaborationRoomDescription": "Collaborate on one or multiple documents with your team",
"FormFilingRoomTitle": "Form Filling Room",
"FillingFormsRoomDescription": "Build, share and fill document templates or work with the ready presets to quickly create documents of any type.",
"PublicRoom": "Public room",
"FillingFormRooms": "Filling form",
"CollaborationRooms": "Collaboration",
"ViewOnlyRooms": "View-only",
"CustomRooms": "Custom",
"FormRoom": "Form room",
"NewRoom": "New room",
"VirtualDataRoomDescription": "Use VDR for advanced file security and transparency while filling and signing documents step-by-step. Set watermarks, automatically index and track all content, restrict downloading and copying.",
"VirtualDataRoom": "Virtual Data Room"
}

View File

@ -136,6 +136,7 @@
"DropzoneTitleSecondary": "или перетащите файл сюда",
"Duplicate": "Дублировать",
"EditButton": "Редактировать",
"EditIndex": "Редактировать индекс",
"Editing": "Редактирование",
"Editor": "Редактор",
"Email": "Email",
@ -411,6 +412,7 @@
"SkipTitle": "Пропустить",
"SomethingWentWrong": "Что-то пошло не так.",
"SortBy": "Сортировать по",
"SortingIndex": "Сортировать индекс",
"SpaceManagement": "Управление Пространством",
"Spaces": "Пространства",
"SpacesInLocalPart": "Имя почтового ящика содержит пробелы",