Merge pull request #526 from ONLYOFFICE/feature/vdr-export-room-index

Feature/vdr export room index
This commit is contained in:
Alexey Safronov 2024-07-10 20:13:53 +04:00 committed by GitHub
commit 46654ecca2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 242 additions and 2 deletions

View File

@ -67,9 +67,12 @@
"EnableLink": "Enable link",
"EnableNotifications": "Enable notifications",
"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.",
"FavoritesEmptyContainerDescription": "To mark files as favorites or remove them from this list, use the context menu.",
"FileContents": "File contents",
"FileDownloadingIsRestricted": "File downloading is restricted in this room.",
"FileExportedToMyDocuments": "file exported to My Documents",
"FileRemoved": "File moved to Trash",
"FileRenamed": "The document '{{oldTitle}}' is renamed to '{{newTitle}}'",
"FilesWillAppearHere": "Files and folders added to the room will appear here.",

View File

@ -0,0 +1,57 @@
// (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 { TFunction } from "i18next";
import { Link, LinkTarget } from "@docspace/shared/components/link";
import { Text } from "@docspace/shared/components/text";
import { toastr } from "@docspace/shared/components/toast";
export const showSuccessExportRoomIndexToast = (
t: TFunction,
fileName: string,
fileUrl: string,
openOnNewPage: boolean,
) => {
const toastMessage = (
<>
<Link
color="#5299E0"
fontSize="12px"
target={openOnNewPage ? LinkTarget.blank : LinkTarget.self}
href={fileUrl}
>
{fileName}
</Link>
&nbsp;
<Text as="span" fontSize="12px">
{t<string>("Files:FileExportedToMyDocuments")}
</Text>
</>
);
toastr.success(toastMessage);
};

View File

@ -80,6 +80,7 @@ import ActionsUploadReactSvgUrl from "PUBLIC_DIR/images/actions.upload.react.svg
import PluginMoreReactSvgUrl from "PUBLIC_DIR/images/plugin.more.react.svg?url";
import CodeReactSvgUrl from "PUBLIC_DIR/images/code.react.svg?url";
import ClearTrashReactSvgUrl from "PUBLIC_DIR/images/clear.trash.react.svg?url";
import ExportRoomIndexSvgUrl from "PUBLIC_DIR/images/icons/16/export-room-index.react.svg?url";
import { getCategoryUrl } from "@docspace/client/src/helpers/utils";
@ -913,6 +914,10 @@ class ContextOptionsStore {
this.filesActionsStore.setMuteAction(action, item, t);
};
onExportRoomIndex = (t, roomId) => {
this.filesActionsStore.exportRoomIndex(t, roomId);
};
onClickRemoveFromRecent = (item) => {
this.filesActionsStore.removeFilesFromRecent([item.id]);
};
@ -1390,6 +1395,7 @@ class ContextOptionsStore {
item.roomType === RoomsType.PublicRoom ||
item.roomType === RoomsType.FormRoom ||
item.roomType === RoomsType.CustomRoom;
const isVDRRoomType = item.roomType === RoomsType.VirtualDataRoom;
const { shared, navigationPath } = this.selectedFolderStore;
@ -1582,6 +1588,14 @@ class ContextOptionsStore {
},
...pinOptions,
...muteOptions,
{
id: "option_export-room-index",
key: "export-room-index",
label: t("Files:ExportRoomIndex"),
icon: ExportRoomIndexSvgUrl,
onClick: () => this.onExportRoomIndex(t, item.id),
disabled: !isVDRRoomType || !item.indexing,
},
{
id: "option_owner-change",
key: "owner-change",

View File

@ -58,6 +58,7 @@ import {
import {
ConflictResolveType,
Events,
ExportRoomIndexTaskStatus,
FileAction,
FileStatus,
FolderType,
@ -88,6 +89,8 @@ import {
import { MEDIA_VIEW_URL } from "@docspace/shared/constants";
import { openingNewTab } from "@docspace/shared/utils/openingNewTab";
import { changeRoomLifetime } from "@docspace/shared/api/rooms";
import api from "@docspace/shared/api";
import { showSuccessExportRoomIndexToast } from "SRC_DIR/helpers/toast-helpers";
class FilesActionStore {
settingsStore;
@ -111,6 +114,7 @@ class FilesActionStore {
isGroupMenuBlocked = false;
emptyTrashInProgress = false;
processCreatingRoomFromData = false;
alreadyExportingRoomIndex = false;
constructor(
settingsStore,
@ -2753,6 +2757,118 @@ class FilesActionStore {
.itemOperationToFolder(operationData)
.catch((error) => toastr.error(error));
};
checkExportRoomIndexProgress = async () => {
return await new Promise((resolve, reject) => {
setTimeout(async () => {
try {
const res = await api.rooms.getExportRoomIndexProgress();
resolve(res);
} catch (e) {
reject(e);
}
}, 1000);
});
};
loopExportRoomIndexStatusChecking = async (pbData) => {
const { setSecondaryProgressBarData } =
this.uploadDataStore.secondaryProgressDataStore;
let isCompleted = false;
let res;
while (!isCompleted) {
res = await this.checkExportRoomIndexProgress();
if (res?.isCompleted) {
isCompleted = true;
}
if (res?.percentage) {
setSecondaryProgressBarData({
icon: pbData.icon,
visible: true,
percent: res.percentage,
label: "",
alert: false,
operationId: pbData.operationId,
});
}
}
return res;
};
checkPreviousExportRoomIndexInProgress = async () => {
try {
if (this.alreadyExportingRoomIndex) {
return true;
}
const previousExport = await api.rooms.getExportRoomIndexProgress();
return previousExport && !previousExport.isCompleted;
} catch (e) {
toastr.error(e);
}
};
onSuccessExportRoomIndex = (t, fileName, fileUrl) => {
const { openOnNewPage } = this.filesSettingsStore;
const urlWithProxy = combineUrl(window.ClientConfig?.proxy?.url, fileUrl);
showSuccessExportRoomIndexToast(t, fileName, urlWithProxy, openOnNewPage);
};
exportRoomIndex = async (t, roomId) => {
const previousExportInProgress =
await this.checkPreviousExportRoomIndexInProgress();
if (previousExportInProgress) {
return toastr.error(t("Files:ExportRoomIndexAlreadyInProgressError"));
}
const { setSecondaryProgressBarData, clearSecondaryProgressData } =
this.uploadDataStore.secondaryProgressDataStore;
const pbData = { icon: "exportIndex", operationId: uniqueid("operation_") };
setSecondaryProgressBarData({
icon: pbData.icon,
visible: true,
percent: 0,
label: "",
alert: false,
operationId: pbData.operationId,
});
this.alreadyExportingRoomIndex = true;
try {
let res = await api.rooms.exportRoomIndex(roomId);
if (!res.isCompleted) {
res = await this.loopExportRoomIndexStatusChecking(pbData);
}
if (res.status === ExportRoomIndexTaskStatus.Failed) {
toastr.error(res.error);
return;
}
if (res.status === ExportRoomIndexTaskStatus.Completed) {
this.onSuccessExportRoomIndex(t, res.resultFileName, res.resultFileUrl);
}
} catch (e) {
toastr.error(e);
} finally {
this.alreadyExportingRoomIndex = false;
setTimeout(() => clearSecondaryProgressData(pbData.operationId), TIMEOUT);
}
};
}
export default FilesActionStore;

View File

@ -2384,6 +2384,7 @@ class FilesStore {
"unpin-room",
"mute-room",
"unmute-room",
"export-room-index",
"separator1",
"download",
"archive-room",

View File

@ -143,6 +143,8 @@ class SelectedFolderStore {
canShare = false;
indexing = false;
parentRoomType: Nullable<FolderType> = null;
lifetime: TRoomLifetime | null = null;
@ -192,6 +194,7 @@ class SelectedFolderStore {
isRootFolder: this.isRootFolder,
parentRoomType: this.parentRoomType,
lifetime: this.lifetime,
indexing: this.indexing,
};
};
@ -238,6 +241,7 @@ class SelectedFolderStore {
this.inRoom = false;
this.parentRoomType = null;
this.lifetime = null;
this.indexing = false;
};
setParentId = (parentId: number) => {

View File

@ -35,7 +35,7 @@ import {
toUrlParams,
} from "../../utils/common";
import RoomsFilter from "./filter";
import { TGetRooms, TRoomLifetime } from "./types";
import { TGetRooms, TRoomLifetime, TExportRoomIndexTask } from "./types";
export async function getRooms(filter: RoomsFilter, signal?: AbortSignal) {
let params;
@ -492,3 +492,17 @@ export function changeRoomLifetime(
return request(options);
}
export function exportRoomIndex(roomId: number) {
return request({
method: "post",
url: `files/rooms/${roomId}/indexexport`,
}) as Promise<TExportRoomIndexTask>;
}
export function getExportRoomIndexProgress() {
return request({
method: "get",
url: `files/rooms/indexexport`,
}) as Promise<TExportRoomIndexTask>;
}

View File

@ -25,7 +25,12 @@
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { TFile, TFolder } from "../files/types";
import { FolderType, RoomsType, ShareAccessRights } from "../../enums";
import {
ExportRoomIndexTaskStatus,
FolderType,
RoomsType,
ShareAccessRights,
} from "../../enums";
import { TCreatedBy, TPathParts } from "../../types";
export type TLogo = {
@ -98,3 +103,14 @@ export type TGetRooms = {
total: number;
new: number;
};
export type TExportRoomIndexTask = {
id: string;
error: string;
percentage: number;
isCompleted: boolean;
status: ExportRoomIndexTaskStatus;
resultFileId: number;
resultFileName: string;
resultFileUrl: string;
};

View File

@ -33,4 +33,5 @@ export const enum FloatingButtonIcons {
plus = "plus",
minus = "minus",
refresh = "refresh",
exportIndex = "exportIndex",
}

View File

@ -37,6 +37,7 @@ import ButtonPlusIcon from "PUBLIC_DIR/images/icons/16/button.plus.react.svg";
import ButtonMinusIcon from "PUBLIC_DIR/images/icons/16/button.minus.react.svg";
import RefreshIcon from "PUBLIC_DIR/images/refresh.react.svg";
import CloseIcon from "PUBLIC_DIR/images/close-icon.react.svg";
import ExportRoomIndexIcon from "PUBLIC_DIR/images/icons/16/export-room-index.react.svg";
import { FloatingButtonTheme } from "./FloatingButton.theme";
@ -67,6 +68,7 @@ const icons = {
minus: <ButtonMinusIcon />,
refresh: <RefreshIcon />,
duplicate: <ButtonDuplicateIcon />,
exportIndex: <ExportRoomIndexIcon />,
};
const FloatingButton = (props: FloatingButtonProps) => {

View File

@ -579,3 +579,11 @@ export const enum LDAPCertificateProblem {
CertUntrustedCa = -2146762478,
CertUnrecognizedError = -2146762477,
}
export const enum ExportRoomIndexTaskStatus {
Created = 0,
Running = 1,
Completed = 2,
Canceled = 3,
Failed = 4,
}

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.8092 8L13.8092 12.3445L14.8555 11.3389L16 12.4388L13.5722 14.7722C13.4205 14.9181 13.2146 15 13 15C12.7854 15 12.5795 14.9181 12.4278 14.7722L10 12.4388L11.1445 11.3389L12.1907 12.3445L12.1907 8.00001L13.8092 8Z" fill="#657077"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 1.5C2 1.22386 1.77614 1 1.5 1H0.5C0.223858 1 0 1.22386 0 1.5V2.5C0 2.77614 0.223858 3 0.5 3H1.5C1.77614 3 2 2.77614 2 2.5V1.5ZM16 1.5C16 1.22386 15.7761 1 15.5 1H4.5C4.22386 1 4 1.22386 4 1.5V2.5C4 2.77614 4.22386 3 4.5 3L15.5 3C15.7761 3 16 2.77614 16 2.5V1.5ZM16 5.5C16 5.22386 15.7761 5 15.5 5H8.5C8.22386 5 8 5.22386 8 5.5V6.5C8 6.77614 8.22386 7 8.5 7H15.5C15.7761 7 16 6.77614 16 6.5V5.5ZM9 13H4.5C4.22386 13 4 13.2239 4 13.5V14.5C4 14.7761 4.22386 15 4.5 15H9V13ZM5 7C5.55228 7 6 6.55228 6 6C6 5.44772 5.55228 5 5 5C4.44772 5 4 5.44772 4 6C4 6.55228 4.44772 7 5 7ZM5 11C5.55228 11 6 10.5523 6 10C6 9.44771 5.55228 9 5 9C4.44772 9 4 9.44771 4 10C4 10.5523 4.44772 11 5 11ZM0.5 13C0.223858 13 0 13.2239 0 13.5V14.5C0 14.7761 0.223858 15 0.5 15H1.5C1.77614 15 2 14.7761 2 14.5V13.5C2 13.2239 1.77614 13 1.5 13H0.5Z" fill="#657077"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB