Merge pull request #526 from ONLYOFFICE/feature/vdr-export-room-index
Feature/vdr export room index
This commit is contained in:
commit
46654ecca2
@ -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.",
|
||||
|
57
packages/client/src/helpers/toast-helpers.tsx
Normal file
57
packages/client/src/helpers/toast-helpers.tsx
Normal 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>
|
||||
|
||||
<Text as="span" fontSize="12px">
|
||||
{t<string>("Files:FileExportedToMyDocuments")}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
|
||||
toastr.success(toastMessage);
|
||||
};
|
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -2384,6 +2384,7 @@ class FilesStore {
|
||||
"unpin-room",
|
||||
"mute-room",
|
||||
"unmute-room",
|
||||
"export-room-index",
|
||||
"separator1",
|
||||
"download",
|
||||
"archive-room",
|
||||
|
@ -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) => {
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -33,4 +33,5 @@ export const enum FloatingButtonIcons {
|
||||
plus = "plus",
|
||||
minus = "minus",
|
||||
refresh = "refresh",
|
||||
exportIndex = "exportIndex",
|
||||
}
|
||||
|
@ -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) => {
|
||||
|
@ -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,
|
||||
}
|
||||
|
4
public/images/icons/16/export-room-index.react.svg
Normal file
4
public/images/icons/16/export-room-index.react.svg
Normal 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 |
Loading…
Reference in New Issue
Block a user