Merge branch 'feature/erasure-table-column' into feature/entries-trash-origin
This commit is contained in:
commit
8629cffd4d
@ -5,7 +5,7 @@
|
||||
"DeleteFolder": "You are about to delete this folder. Are you sure you want to continue?",
|
||||
"DeleteFile": "You are about to delete this file. Are you sure you want to continue?",
|
||||
"MoveToTrashButton": "Move to Trash",
|
||||
"MoveToTrashFile": "You are about to delete this file. Please note that if you have shared it with someone, it will become unavailable. Are you sure you want to continue?",
|
||||
"MoveToTrashFile": "You are about to delete this file. Please note that if you have shared it with someone, it will become unavailable. The file will be permanently deleted in 30 days. Are you sure you want to continue?",
|
||||
"MoveToTrashFileFromPersonal": "You are about to delete this file. Are you sure you want to continue?",
|
||||
"MoveToTrashFolder": "You are about to delete this folder. Please note that if you have shared it with someone, it will become unavailable. Are you sure you want to continue?",
|
||||
"MoveToTrashFolderFromPersonal": "You are about to delete this folder. Are you sure you want to continue?",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"BackToParentFolderButton": "Back to parent folder",
|
||||
"ByAuthor": "Author",
|
||||
"ByCreation": "Created",
|
||||
"ByErasure": "Erasure",
|
||||
"ByLastModified": "Modified",
|
||||
"ByOwner": "Owner",
|
||||
"CollaborationRooms": "Collaboration",
|
||||
@ -22,6 +23,7 @@
|
||||
"CopyItems": "<strong>{{qty}}</strong> elements copied",
|
||||
"CreateRoom": "Create room",
|
||||
"CustomRooms": "Custom",
|
||||
"DaysRemaining": "Days remaining: {{daysRemaining}}",
|
||||
"Document": "Document",
|
||||
"EditRoom": "Edit room",
|
||||
"EmptyFile": "Empty file",
|
||||
@ -100,7 +102,8 @@
|
||||
"TooltipElementCopyMessage": "Copy {{element}}",
|
||||
"TooltipElementsCopyMessage": "Copy {{element}} elements",
|
||||
"TooltipElementsMoveMessage": "Move {{element}} elements",
|
||||
"TrashEmptyDescription": "All deleted files are moved to 'Trash'. Restore files deleted by mistake or delete them permanently. Please note, that the files deleted from the 'Trash' cannot be restored any longer.",
|
||||
"TrashErasureWarning": "Items in Trash are automatically deleted after 30 days",
|
||||
"TrashEmptyDescription": "All deleted files are moved to 'Trash'. Restore files deleted by mistake or delete them permanently. Files in 'Trash' are automatically deleted after 30 days. Please note, that the files deleted from the 'Trash' cannot be restored any longer.",
|
||||
"UnarchivedRoomAction": "The room '{{name}}' is unarchived",
|
||||
"UnarchivedRoomsAction": "The rooms are unarchived",
|
||||
"UnblockVersion": "Unblock/Check-in",
|
||||
|
@ -75,7 +75,7 @@
|
||||
"DeleteThemeNotice": "The theme will be deleted permanently. You will not be able to undo this action.",
|
||||
"DeveloperTools": "Developer",
|
||||
"Disabled": "Disabled",
|
||||
"DownloadCopy": "Download the copy",
|
||||
"DownloadCopy": "Download copy",
|
||||
"DownloadReportBtnText": "Download report",
|
||||
"DownloadReportDescription": "The report will be saved to My Documents",
|
||||
"DownloadStatisticsText": "You can download the report for the data available during the selected storage period to view the detailed statistics.",
|
||||
@ -86,6 +86,7 @@
|
||||
"EnableAutomaticBackup": "Enable automatic backup.",
|
||||
"EnableAutomaticBackupDescription": "Use this option to back up the space data.",
|
||||
"EnterTitle": "Enter title",
|
||||
"EnterPath": "Enter path",
|
||||
"EveryDay": "Every day",
|
||||
"EveryMonth": "Every month",
|
||||
"EveryWeek": "Every week",
|
||||
|
@ -85,6 +85,7 @@
|
||||
"EnableAutomaticBackup": "Давать возможность автоматически копировать данные",
|
||||
"EnableAutomaticBackupDescription": "Используйте эту опцию для выполнения резервного копирования данных портала.",
|
||||
"EnterTitle": "Укажите название",
|
||||
"EnterPath": "Введите путь",
|
||||
"EveryDay": "Каждый день",
|
||||
"EveryMonth": "Каждый месяц",
|
||||
"EveryWeek": "Каждую неделю",
|
||||
|
@ -51,6 +51,7 @@ const MobileView = ({
|
||||
clearSecondaryProgressData,
|
||||
onMainButtonClick,
|
||||
isRoomsFolder,
|
||||
mainButtonMobileVisible,
|
||||
}) => {
|
||||
const [isOpenButton, setIsOpenButton] = React.useState(false);
|
||||
const [percentProgress, setPercentProgress] = React.useState(0);
|
||||
@ -154,22 +155,26 @@ const MobileView = ({
|
||||
]);
|
||||
|
||||
return (
|
||||
<StyledMainButtonMobile
|
||||
actionOptions={actionOptions}
|
||||
isOpenButton={isOpenButton}
|
||||
onUploadClick={openButtonToggler}
|
||||
onClose={openButtonToggler}
|
||||
buttonOptions={buttonOptions}
|
||||
percent={percentProgress}
|
||||
progressOptions={progressOptions}
|
||||
title={titleProp}
|
||||
withoutButton={isRooms}
|
||||
alert={primaryProgressDataAlert}
|
||||
withMenu={!isRooms}
|
||||
onClick={onMainButtonClick}
|
||||
onAlertClick={showUploadPanel}
|
||||
withAlertClick={isRoomsFolder}
|
||||
/>
|
||||
<>
|
||||
{mainButtonMobileVisible && (
|
||||
<StyledMainButtonMobile
|
||||
actionOptions={actionOptions}
|
||||
isOpenButton={isOpenButton}
|
||||
onUploadClick={openButtonToggler}
|
||||
onClose={openButtonToggler}
|
||||
buttonOptions={buttonOptions}
|
||||
percent={percentProgress}
|
||||
progressOptions={progressOptions}
|
||||
title={titleProp}
|
||||
withoutButton={isRooms}
|
||||
alert={primaryProgressDataAlert}
|
||||
withMenu={!isRooms}
|
||||
onClick={onMainButtonClick}
|
||||
onAlertClick={showUploadPanel}
|
||||
withAlertClick={isRoomsFolder}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -111,6 +111,8 @@ const ArticleMainButtonContent = (props) => {
|
||||
canCreateFiles,
|
||||
|
||||
setInvitePanelOptions,
|
||||
|
||||
mainButtonMobileVisible,
|
||||
} = props;
|
||||
|
||||
const isAccountsPage = selectedTreeNode[0] === "accounts";
|
||||
@ -443,6 +445,7 @@ const ArticleMainButtonContent = (props) => {
|
||||
actionOptions={actions}
|
||||
buttonOptions={uploadActions}
|
||||
isRooms={isRoomsFolder}
|
||||
mainButtonMobileVisible={mainButtonMobileVisible}
|
||||
onMainButtonClick={onCreateRoom}
|
||||
/>
|
||||
)}
|
||||
@ -508,7 +511,13 @@ export default inject(
|
||||
selectedFolderStore,
|
||||
accessRightsStore,
|
||||
}) => {
|
||||
const { isLoaded, firstLoad, isLoading, canCreate } = filesStore;
|
||||
const {
|
||||
isLoaded,
|
||||
firstLoad,
|
||||
isLoading,
|
||||
canCreate,
|
||||
mainButtonMobileVisible,
|
||||
} = filesStore;
|
||||
const {
|
||||
isPrivacyFolder,
|
||||
isFavoritesFolder,
|
||||
@ -564,6 +573,8 @@ export default inject(
|
||||
isAdmin,
|
||||
isOwner,
|
||||
isVisitor,
|
||||
|
||||
mainButtonMobileVisible,
|
||||
};
|
||||
}
|
||||
)(
|
||||
|
@ -58,9 +58,10 @@ export const CategoryType = Object.freeze({
|
||||
* @readonly
|
||||
*/
|
||||
export const TableVersions = Object.freeze({
|
||||
Files: "2",
|
||||
Rooms: "1",
|
||||
Files: "2",
|
||||
Accounts: "3",
|
||||
Trash: "4",
|
||||
});
|
||||
|
||||
export const BINDING_POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
|
||||
|
@ -11,6 +11,7 @@ const FilesMediaViewer = (props) => {
|
||||
t,
|
||||
files,
|
||||
playlist,
|
||||
currentPostionIndex,
|
||||
visible,
|
||||
currentMediaFileId,
|
||||
deleteItemAction,
|
||||
@ -43,10 +44,19 @@ const FilesMediaViewer = (props) => {
|
||||
extsMediaPreviewed,
|
||||
setIsPreview,
|
||||
isPreview,
|
||||
nextMedia,
|
||||
prevMedia,
|
||||
resetUrl,
|
||||
firstLoad,
|
||||
setSelection,
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
resetSelection();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
useEffect(() => {
|
||||
const previewId = queryString.parse(location.search).preview;
|
||||
|
||||
@ -88,6 +98,10 @@ const FilesMediaViewer = (props) => {
|
||||
window.history.pushState(null, null, url);
|
||||
};
|
||||
|
||||
const resetSelection = () => {
|
||||
setSelection([]);
|
||||
};
|
||||
|
||||
const removeQuery = (queryName) => {
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
|
||||
@ -108,9 +122,6 @@ const FilesMediaViewer = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const canDelete = (fileId) => true; //TODO:
|
||||
const canDownload = (fileId) => true; //TODO:
|
||||
|
||||
const onDeleteMediaFile = (id) => {
|
||||
const translations = {
|
||||
deleteOperation: t("Translations:DeleteOperation"),
|
||||
@ -138,22 +149,12 @@ const FilesMediaViewer = (props) => {
|
||||
if (isPreview) {
|
||||
setIsPreview(false);
|
||||
resetUrl();
|
||||
setScrollToItem({ id: previewFile.id, type: "file" });
|
||||
setBufferSelection(previewFile);
|
||||
if (previewFile) {
|
||||
setScrollToItem({ id: previewFile.id, type: "file" });
|
||||
setBufferSelection(previewFile);
|
||||
}
|
||||
setToPreviewFile(null);
|
||||
}
|
||||
// if (previewFile) {
|
||||
// setIsLoading(true);
|
||||
// setFirstLoad(true);
|
||||
|
||||
// fetchFiles(previewFile.folderId).finally(() => {
|
||||
// setIsLoading(false);
|
||||
// setFirstLoad(false);
|
||||
// setScrollToItem({ id: previewFile.id, type: "file" });
|
||||
// setBufferSelection(previewFile);
|
||||
// setToPreviewFile(null);
|
||||
// });
|
||||
// }
|
||||
|
||||
setMediaViewerData({ visible: false, id: null });
|
||||
|
||||
@ -176,20 +177,16 @@ const FilesMediaViewer = (props) => {
|
||||
t={t}
|
||||
userAccess={userAccess}
|
||||
currentFileId={currentMediaFileId}
|
||||
allowConvert={true} //TODO:
|
||||
canDelete={canDelete} //TODO:
|
||||
canDownload={canDownload} //TODO:
|
||||
visible={visible}
|
||||
playlist={playlist}
|
||||
playlistPos={currentPostionIndex}
|
||||
onDelete={onDeleteMediaFile}
|
||||
onDownload={onDownloadMediaFile}
|
||||
onClickFavorite={onClickFavorite}
|
||||
setBufferSelection={setBufferSelection}
|
||||
archiveRoomsId={archiveRoomsId}
|
||||
files={files}
|
||||
onClickDownload={onClickDownload}
|
||||
onShowInfoPanel={onShowInfoPanel}
|
||||
onClickDownloadAs={onClickDownloadAs}
|
||||
onClickDelete={onClickDelete}
|
||||
onClickRename={onClickRename}
|
||||
onMoveAction={onMoveAction}
|
||||
@ -201,11 +198,10 @@ const FilesMediaViewer = (props) => {
|
||||
deleteDialogVisible={deleteDialogVisible}
|
||||
extsMediaPreviewed={extsMediaPreviewed}
|
||||
extsImagePreviewed={extsImagePreviewed}
|
||||
errorLabel={t("Translations:MediaLoadError")}
|
||||
isPreviewFile={firstLoad}
|
||||
previewFile={previewFile}
|
||||
onChangeUrl={onChangeUrl}
|
||||
isFavoritesFolder={isFavoritesFolder}
|
||||
nextMedia={nextMedia}
|
||||
prevMedia={prevMedia}
|
||||
/>
|
||||
)
|
||||
);
|
||||
@ -233,15 +229,19 @@ export default inject(
|
||||
setIsPreview,
|
||||
isPreview,
|
||||
resetUrl,
|
||||
setSelection,
|
||||
} = filesStore;
|
||||
const {
|
||||
visible,
|
||||
id: currentMediaFileId,
|
||||
currentPostionIndex,
|
||||
setMediaViewerData,
|
||||
playlist,
|
||||
previewFile,
|
||||
setToPreviewFile,
|
||||
setCurrentId,
|
||||
nextMedia,
|
||||
prevMedia,
|
||||
} = mediaViewerDataStore;
|
||||
const { deleteItemAction } = filesActionsStore;
|
||||
const { getIcon, extsImagePreviewed, extsMediaPreviewed } = settingsStore;
|
||||
@ -262,6 +262,9 @@ export default inject(
|
||||
return {
|
||||
files,
|
||||
playlist,
|
||||
currentPostionIndex,
|
||||
nextMedia,
|
||||
prevMedia,
|
||||
userAccess,
|
||||
visible: playlist.length > 0 && visible,
|
||||
currentMediaFileId,
|
||||
@ -295,6 +298,7 @@ export default inject(
|
||||
onCopyAction,
|
||||
onDuplicate,
|
||||
archiveRoomsId,
|
||||
setSelection,
|
||||
};
|
||||
}
|
||||
)(
|
||||
|
@ -116,6 +116,7 @@ const Table = ({
|
||||
hasMoreFiles,
|
||||
filterTotal,
|
||||
isRooms,
|
||||
isTrashFolder,
|
||||
withPaging,
|
||||
columnStorageName,
|
||||
columnInfoPanelStorageName,
|
||||
@ -206,6 +207,7 @@ const Table = ({
|
||||
theme={theme}
|
||||
tagCount={tagCount}
|
||||
isRooms={isRooms}
|
||||
isTrashFolder={isTrashFolder}
|
||||
hideColumns={hideColumns}
|
||||
setUploadedFileIdWithVersion={setUploadedFileIdWithVersion}
|
||||
/>
|
||||
@ -218,7 +220,7 @@ const Table = ({
|
||||
export default inject(({ filesStore, treeFoldersStore, auth, tableStore }) => {
|
||||
const { isVisible: infoPanelVisible } = auth.infoPanelStore;
|
||||
|
||||
const { isRoomsFolder, isArchiveFolder } = treeFoldersStore;
|
||||
const { isRoomsFolder, isArchiveFolder, isTrashFolder } = treeFoldersStore;
|
||||
const isRooms = isRoomsFolder || isArchiveFolder;
|
||||
|
||||
const { columnStorageName, columnInfoPanelStorageName } = tableStore;
|
||||
@ -251,6 +253,7 @@ export default inject(({ filesStore, treeFoldersStore, auth, tableStore }) => {
|
||||
hasMoreFiles,
|
||||
filterTotal: isRooms ? roomsFilterTotal : filterTotal,
|
||||
isRooms,
|
||||
isTrashFolder,
|
||||
withPaging,
|
||||
columnStorageName,
|
||||
columnInfoPanelStorageName,
|
||||
|
@ -14,10 +14,12 @@ class FilesTableHeader extends React.Component {
|
||||
}
|
||||
|
||||
getTableColumns = (fromUpdate = false) => {
|
||||
const { t, isRooms, getColumns } = this.props;
|
||||
const { t, isRooms, isTrashFolder, getColumns } = this.props;
|
||||
|
||||
const defaultColumns = [];
|
||||
|
||||
console.log("GET TABLE COLUMNS");
|
||||
|
||||
if (isRooms) {
|
||||
const columns = [
|
||||
{
|
||||
@ -68,7 +70,72 @@ class FilesTableHeader extends React.Component {
|
||||
onClick: this.onRoomsFilter,
|
||||
},
|
||||
];
|
||||
|
||||
defaultColumns.push(...columns);
|
||||
} else if (isTrashFolder) {
|
||||
const columns = [
|
||||
{
|
||||
key: "Name",
|
||||
title: t("Common:Name"),
|
||||
resizable: true,
|
||||
enable: this.props.nameColumnIsEnabled,
|
||||
default: true,
|
||||
sortBy: "AZ",
|
||||
minWidth: 210,
|
||||
onClick: this.onFilter,
|
||||
},
|
||||
{
|
||||
key: "Author",
|
||||
title: t("ByAuthor"),
|
||||
enable: this.props.authorColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "Author",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Created",
|
||||
title: t("ByCreation"),
|
||||
enable: this.props.createdColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "DateAndTimeCreation",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Erasure",
|
||||
title: t("ByErasure"),
|
||||
enable: this.props.erasureColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "DateAndTime",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Size",
|
||||
title: t("Common:Size"),
|
||||
enable: this.props.sizeColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "Size",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Type",
|
||||
title: t("Common:Type"),
|
||||
enable: this.props.typeColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "Type",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "QuickButtons",
|
||||
title: "",
|
||||
enable: this.props.quickButtonsColumnIsEnabled,
|
||||
defaultSize: 75,
|
||||
resizable: false,
|
||||
},
|
||||
];
|
||||
defaultColumns.push(...columns);
|
||||
} else {
|
||||
const columns = [
|
||||
@ -135,7 +202,6 @@ class FilesTableHeader extends React.Component {
|
||||
resizable: false,
|
||||
},
|
||||
];
|
||||
|
||||
defaultColumns.push(...columns);
|
||||
}
|
||||
|
||||
@ -194,7 +260,10 @@ class FilesTableHeader extends React.Component {
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.isRooms !== prevProps.isRooms) {
|
||||
if (
|
||||
this.props.isRooms !== prevProps.isRooms ||
|
||||
this.props.isTrashFolder !== prevProps.isTrashFolder
|
||||
) {
|
||||
return this.getTableColumns(true);
|
||||
}
|
||||
|
||||
@ -352,7 +421,12 @@ export default inject(
|
||||
roomsFilter,
|
||||
fetchRooms,
|
||||
} = filesStore;
|
||||
const { isRecentFolder, isRoomsFolder, isArchiveFolder } = treeFoldersStore;
|
||||
const {
|
||||
isRecentFolder,
|
||||
isRoomsFolder,
|
||||
isArchiveFolder,
|
||||
isTrashFolder,
|
||||
} = treeFoldersStore;
|
||||
const isRooms = isRoomsFolder || isArchiveFolder;
|
||||
const withContent = canShare;
|
||||
const sortingVisible = !isRecentFolder;
|
||||
@ -371,6 +445,7 @@ export default inject(
|
||||
authorColumnIsEnabled,
|
||||
createdColumnIsEnabled,
|
||||
modifiedColumnIsEnabled,
|
||||
erasureColumnIsEnabled,
|
||||
sizeColumnIsEnabled,
|
||||
typeColumnIsEnabled,
|
||||
quickButtonsColumnIsEnabled,
|
||||
@ -416,6 +491,7 @@ export default inject(
|
||||
authorColumnIsEnabled,
|
||||
createdColumnIsEnabled,
|
||||
modifiedColumnIsEnabled,
|
||||
erasureColumnIsEnabled,
|
||||
sizeColumnIsEnabled,
|
||||
typeColumnIsEnabled,
|
||||
quickButtonsColumnIsEnabled,
|
||||
@ -429,6 +505,7 @@ export default inject(
|
||||
getColumns,
|
||||
setColumnEnable,
|
||||
isRooms,
|
||||
isTrashFolder,
|
||||
};
|
||||
}
|
||||
)(
|
||||
|
@ -8,6 +8,7 @@ import ItemIcon from "../../../../../components/ItemIcon";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { classNames } from "@docspace/components/utils/classNames";
|
||||
import RoomsRowDataComponent from "./sub-components/RoomsRowData";
|
||||
import TrashRowDataComponent from "./sub-components/TrashRowData";
|
||||
import RowDataComponent from "./sub-components/RowData";
|
||||
import { StyledTableRow, StyledDragAndDrop } from "./StyledTable";
|
||||
|
||||
@ -37,6 +38,7 @@ const FilesTableRow = (props) => {
|
||||
showHotkeyBorder,
|
||||
id,
|
||||
isRooms,
|
||||
isTrashFolder,
|
||||
setUploadedFileIdWithVersion,
|
||||
} = props;
|
||||
const [isHighlight, setIsHighlight] = React.useState(false);
|
||||
@ -173,6 +175,12 @@ const FilesTableRow = (props) => {
|
||||
dragStyles={dragStyles}
|
||||
{...props}
|
||||
/>
|
||||
) : isTrashFolder ? (
|
||||
<TrashRowDataComponent
|
||||
element={element}
|
||||
dragStyles={dragStyles}
|
||||
{...props}
|
||||
/>
|
||||
) : (
|
||||
<RowDataComponent
|
||||
element={element}
|
||||
|
@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { StyledText } from "./CellStyles";
|
||||
import moment from "moment";
|
||||
|
||||
const ErasureCell = ({ t, updatedDate, sideColor }) => {
|
||||
const daysRemaining = 30 - moment().diff(updatedDate, "days");
|
||||
const title = t("Files:DaysRemaining", { daysRemaining });
|
||||
|
||||
return (
|
||||
<StyledText
|
||||
title={title}
|
||||
fontSize="12px"
|
||||
fontWeight={600}
|
||||
color={sideColor}
|
||||
className="row_update-text"
|
||||
truncate
|
||||
>
|
||||
{title}
|
||||
</StyledText>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErasureCell;
|
@ -11,6 +11,7 @@ import {
|
||||
StyledBadgesContainer,
|
||||
StyledQuickButtonsContainer,
|
||||
} from "../StyledTable";
|
||||
import ErasureCell from "./ErasureCell";
|
||||
|
||||
const RowDataComponent = (props) => {
|
||||
const {
|
||||
|
@ -0,0 +1,187 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import TableCell from "@docspace/components/table-container/TableCell";
|
||||
import FileNameCell from "./FileNameCell";
|
||||
import TypeCell from "./TypeCell";
|
||||
import AuthorCell from "./AuthorCell";
|
||||
import DateCell from "./DateCell";
|
||||
import SizeCell from "./SizeCell";
|
||||
import { classNames } from "@docspace/components/utils/classNames";
|
||||
import {
|
||||
StyledBadgesContainer,
|
||||
StyledQuickButtonsContainer,
|
||||
} from "../StyledTable";
|
||||
import ErasureCell from "./ErasureCell";
|
||||
|
||||
const TrashRowDataComponent = (props) => {
|
||||
const {
|
||||
authorColumnIsEnabled,
|
||||
createdColumnIsEnabled,
|
||||
erasureColumnIsEnabled,
|
||||
sizeColumnIsEnabled,
|
||||
typeColumnIsEnabled,
|
||||
quickButtonsColumnIsEnabled,
|
||||
|
||||
dragStyles,
|
||||
selectionProp,
|
||||
value,
|
||||
theme,
|
||||
onContentFileSelect,
|
||||
checkedProps,
|
||||
element,
|
||||
inProgress,
|
||||
showHotkeyBorder,
|
||||
badgesComponent,
|
||||
quickButtonsComponent,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableCell
|
||||
{...dragStyles}
|
||||
className={classNames(
|
||||
selectionProp?.className,
|
||||
"table-container_file-name-cell"
|
||||
)}
|
||||
value={value}
|
||||
>
|
||||
<FileNameCell
|
||||
theme={theme}
|
||||
onContentSelect={onContentFileSelect}
|
||||
checked={checkedProps}
|
||||
element={element}
|
||||
inProgress={inProgress}
|
||||
{...props}
|
||||
/>
|
||||
<StyledBadgesContainer showHotkeyBorder={showHotkeyBorder}>
|
||||
{badgesComponent}
|
||||
</StyledBadgesContainer>
|
||||
</TableCell>
|
||||
|
||||
{authorColumnIsEnabled ? (
|
||||
<TableCell
|
||||
style={
|
||||
!authorColumnIsEnabled ? { background: "none" } : dragStyles.style
|
||||
}
|
||||
{...selectionProp}
|
||||
>
|
||||
<AuthorCell
|
||||
sideColor={theme.filesSection.tableView.row.sideColor}
|
||||
{...props}
|
||||
/>
|
||||
</TableCell>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
|
||||
{createdColumnIsEnabled ? (
|
||||
<TableCell
|
||||
style={
|
||||
!createdColumnIsEnabled
|
||||
? { background: "none !important" }
|
||||
: dragStyles.style
|
||||
}
|
||||
{...selectionProp}
|
||||
>
|
||||
<DateCell
|
||||
create
|
||||
sideColor={theme.filesSection.tableView.row.sideColor}
|
||||
{...props}
|
||||
/>
|
||||
</TableCell>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
|
||||
{erasureColumnIsEnabled ? (
|
||||
<TableCell
|
||||
style={
|
||||
!erasureColumnIsEnabled ? { background: "none" } : dragStyles.style
|
||||
}
|
||||
{...selectionProp}
|
||||
>
|
||||
<ErasureCell
|
||||
sideColor={theme.filesSection.tableView.row.sideColor}
|
||||
{...props}
|
||||
/>
|
||||
</TableCell>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
|
||||
{sizeColumnIsEnabled ? (
|
||||
<TableCell
|
||||
style={
|
||||
!sizeColumnIsEnabled ? { background: "none" } : dragStyles.style
|
||||
}
|
||||
{...selectionProp}
|
||||
>
|
||||
<SizeCell
|
||||
sideColor={theme.filesSection.tableView.row.sideColor}
|
||||
{...props}
|
||||
/>
|
||||
</TableCell>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
|
||||
{typeColumnIsEnabled ? (
|
||||
<TableCell
|
||||
style={
|
||||
!typeColumnIsEnabled
|
||||
? { background: "none !important" }
|
||||
: dragStyles.style
|
||||
}
|
||||
{...selectionProp}
|
||||
>
|
||||
<TypeCell
|
||||
sideColor={theme.filesSection.tableView.row.sideColor}
|
||||
{...props}
|
||||
/>
|
||||
</TableCell>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
{quickButtonsColumnIsEnabled ? (
|
||||
<TableCell
|
||||
style={
|
||||
!quickButtonsColumnIsEnabled
|
||||
? { background: "none" }
|
||||
: dragStyles.style
|
||||
}
|
||||
{...selectionProp}
|
||||
className={classNames(
|
||||
selectionProp?.className,
|
||||
"table-container_quick-buttons-wrapper"
|
||||
)}
|
||||
>
|
||||
<StyledQuickButtonsContainer>
|
||||
{quickButtonsComponent}
|
||||
</StyledQuickButtonsContainer>
|
||||
</TableCell>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject(({ tableStore }) => {
|
||||
const {
|
||||
authorColumnIsEnabled,
|
||||
createdColumnIsEnabled,
|
||||
erasureColumnIsEnabled,
|
||||
sizeColumnIsEnabled,
|
||||
typeColumnIsEnabled,
|
||||
quickButtonsColumnIsEnabled,
|
||||
} = tableStore;
|
||||
|
||||
return {
|
||||
authorColumnIsEnabled,
|
||||
createdColumnIsEnabled,
|
||||
erasureColumnIsEnabled,
|
||||
sizeColumnIsEnabled,
|
||||
typeColumnIsEnabled,
|
||||
quickButtonsColumnIsEnabled,
|
||||
};
|
||||
})(observer(TrashRowDataComponent));
|
@ -0,0 +1,169 @@
|
||||
export const filesColumns = [
|
||||
{
|
||||
key: "Name",
|
||||
title: t("Common:Name"),
|
||||
resizable: true,
|
||||
enable: this.props.nameColumnIsEnabled,
|
||||
default: true,
|
||||
sortBy: "AZ",
|
||||
minWidth: 210,
|
||||
onClick: this.onFilter,
|
||||
},
|
||||
{
|
||||
key: "Author",
|
||||
title: t("ByAuthor"),
|
||||
enable: this.props.authorColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "Author",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Created",
|
||||
title: t("ByCreation"),
|
||||
enable: this.props.createdColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "DateAndTimeCreation",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Modified",
|
||||
title: t("ByLastModified"),
|
||||
enable: this.props.modifiedColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "DateAndTime",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Size",
|
||||
title: t("Common:Size"),
|
||||
enable: this.props.sizeColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "Size",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Type",
|
||||
title: t("Common:Type"),
|
||||
enable: this.props.typeColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "Type",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "QuickButtons",
|
||||
title: "",
|
||||
enable: this.props.quickButtonsColumnIsEnabled,
|
||||
defaultSize: 75,
|
||||
resizable: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const roomsColumns = [
|
||||
{
|
||||
key: "Name",
|
||||
title: t("Common:Name"),
|
||||
resizable: true,
|
||||
enable: this.props.nameColumnIsEnabled,
|
||||
default: true,
|
||||
sortBy: "AZ",
|
||||
minWidth: 210,
|
||||
onClick: this.onFilter,
|
||||
},
|
||||
{
|
||||
key: "Author",
|
||||
title: t("ByAuthor"),
|
||||
enable: this.props.authorColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "Author",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Created",
|
||||
title: t("ByCreation"),
|
||||
enable: this.props.createdColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "DateAndTimeCreation",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Modified",
|
||||
title: t("ByLastModified"),
|
||||
enable: this.props.modifiedColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "DateAndTime",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
];
|
||||
|
||||
export const trashColumns = [
|
||||
{
|
||||
key: "Name",
|
||||
title: t("Common:Name"),
|
||||
resizable: true,
|
||||
enable: this.props.nameColumnIsEnabled,
|
||||
default: true,
|
||||
sortBy: "AZ",
|
||||
minWidth: 210,
|
||||
onClick: this.onFilter,
|
||||
},
|
||||
{
|
||||
key: "Author",
|
||||
title: t("ByAuthor"),
|
||||
enable: this.props.authorColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "Author",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Created",
|
||||
title: t("ByCreation"),
|
||||
enable: this.props.createdColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "DateAndTimeCreation",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Erasure",
|
||||
title: t("ByErasure"),
|
||||
enable: this.props.erasureColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "DateAndTime",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Size",
|
||||
title: t("Common:Size"),
|
||||
enable: this.props.sizeColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "Size",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "Type",
|
||||
title: t("Common:Type"),
|
||||
enable: this.props.typeColumnIsEnabled,
|
||||
resizable: true,
|
||||
sortBy: "Type",
|
||||
onClick: this.onFilter,
|
||||
onChange: this.onColumnChange,
|
||||
},
|
||||
{
|
||||
key: "QuickButtons",
|
||||
title: "",
|
||||
enable: this.props.quickButtonsColumnIsEnabled,
|
||||
defaultSize: 75,
|
||||
resizable: false,
|
||||
},
|
||||
];
|
@ -5,6 +5,7 @@ import { inject, observer } from "mobx-react";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import { withRouter } from "react-router";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
import find from "lodash/find";
|
||||
import result from "lodash/result";
|
||||
|
||||
@ -146,6 +147,10 @@ const TABLE_ROOMS_COLUMNS = `roomsTableColumns_ver-${TableVersions.Rooms}`;
|
||||
|
||||
const COLUMNS_ROOMS_SIZE_INFO_PANEL = `roomsColumnsSizeInfoPanel_ver-${TableVersions.Rooms}`;
|
||||
|
||||
const TABLE_TRASH_COLUMNS = `trashTableColumns_ver-${TableVersions.Trash}`;
|
||||
|
||||
const COLUMNS_TRASH_SIZE_INFO_PANEL = `trashColumnsSizeInfoPanel_ver-${TableVersions.Trash}`;
|
||||
|
||||
const SectionFilterContent = ({
|
||||
t,
|
||||
filter,
|
||||
@ -164,6 +169,7 @@ const SectionFilterContent = ({
|
||||
fetchTags,
|
||||
infoPanelVisible,
|
||||
isRooms,
|
||||
isTrash,
|
||||
userId,
|
||||
isPersonalRoom,
|
||||
setCurrentRoomsFilter,
|
||||
@ -175,6 +181,7 @@ const SectionFilterContent = ({
|
||||
isEmptyPage,
|
||||
clearSearch,
|
||||
setClearSearch,
|
||||
setMainButtonMobileVisible,
|
||||
}) => {
|
||||
const [selectedFilterValues, setSelectedFilterValues] = React.useState(null);
|
||||
const [isLoadedFilter, setIsLoadedFilter] = React.useState(false);
|
||||
@ -1002,13 +1009,13 @@ const SectionFilterContent = ({
|
||||
|
||||
return filterOptions;
|
||||
}, [
|
||||
isFavoritesFolder,
|
||||
isRecentFolder,
|
||||
isRooms,
|
||||
t,
|
||||
personal,
|
||||
isPersonalRoom,
|
||||
providers,
|
||||
isPersonalRoom,
|
||||
isRooms,
|
||||
isFavoritesFolder,
|
||||
isRecentFolder,
|
||||
]);
|
||||
|
||||
const getViewSettingsData = React.useCallback(() => {
|
||||
@ -1076,6 +1083,12 @@ const SectionFilterContent = ({
|
||||
label: t("Common:Owner"),
|
||||
default: true,
|
||||
};
|
||||
const erasure = {
|
||||
id: "sort-by_erasure",
|
||||
key: "Erasure",
|
||||
label: t("ByErasure"),
|
||||
default: true,
|
||||
};
|
||||
const tags = {
|
||||
id: "sort-by_tags",
|
||||
key: "Tags",
|
||||
@ -1140,6 +1153,60 @@ const SectionFilterContent = ({
|
||||
|
||||
!hide && commonOptions.push(modifiedDate);
|
||||
}
|
||||
} else if (isTrash) {
|
||||
const availableSort = localStorage
|
||||
?.getItem(`${TABLE_TRASH_COLUMNS}=${userId}`)
|
||||
?.split(",");
|
||||
|
||||
const infoPanelColumnsSize = localStorage
|
||||
?.getItem(`${COLUMNS_TRASH_SIZE_INFO_PANEL}=${userId}`)
|
||||
?.split(" ");
|
||||
|
||||
if (availableSort?.includes("Author")) {
|
||||
const idx = availableSort.findIndex((x) => x === "Author");
|
||||
const hide =
|
||||
infoPanelVisible &&
|
||||
infoPanelColumnsSize &&
|
||||
infoPanelColumnsSize[idx] === "0px";
|
||||
|
||||
!hide && commonOptions.push(authorOption);
|
||||
}
|
||||
if (availableSort?.includes("Created")) {
|
||||
const idx = availableSort.findIndex((x) => x === "Created");
|
||||
const hide =
|
||||
infoPanelVisible &&
|
||||
infoPanelColumnsSize &&
|
||||
infoPanelColumnsSize[idx] === "0px";
|
||||
|
||||
!hide && commonOptions.push(creationDate);
|
||||
}
|
||||
if (availableSort?.includes("Erasure")) {
|
||||
const idx = availableSort.findIndex((x) => x === "Erasure");
|
||||
const hide =
|
||||
infoPanelVisible &&
|
||||
infoPanelColumnsSize &&
|
||||
infoPanelColumnsSize[idx] === "0px";
|
||||
|
||||
!hide && commonOptions.push(erasure);
|
||||
}
|
||||
if (availableSort?.includes("Size")) {
|
||||
const idx = availableSort.findIndex((x) => x === "Size");
|
||||
const hide =
|
||||
infoPanelVisible &&
|
||||
infoPanelColumnsSize &&
|
||||
infoPanelColumnsSize[idx] === "0px";
|
||||
|
||||
!hide && commonOptions.push(size);
|
||||
}
|
||||
if (availableSort?.includes("Type")) {
|
||||
const idx = availableSort.findIndex((x) => x === "Type");
|
||||
const hide =
|
||||
infoPanelVisible &&
|
||||
infoPanelColumnsSize &&
|
||||
infoPanelColumnsSize[idx] === "0px";
|
||||
|
||||
!hide && commonOptions.push(type);
|
||||
}
|
||||
} else {
|
||||
const availableSort = localStorage
|
||||
?.getItem(`${TABLE_COLUMNS}=${userId}`)
|
||||
@ -1201,6 +1268,12 @@ const SectionFilterContent = ({
|
||||
commonOptions.push(tags);
|
||||
commonOptions.push(owner);
|
||||
commonOptions.push(modifiedDate);
|
||||
} else if (isTrash) {
|
||||
commonOptions.push(authorOption);
|
||||
commonOptions.push(creationDate);
|
||||
commonOptions.push(erasure);
|
||||
commonOptions.push(size);
|
||||
commonOptions.push(type);
|
||||
} else {
|
||||
commonOptions.push(authorOption);
|
||||
commonOptions.push(creationDate);
|
||||
@ -1303,6 +1376,12 @@ const SectionFilterContent = ({
|
||||
]
|
||||
);
|
||||
|
||||
const onSortButtonClick = (isOpen) => {
|
||||
if (isMobileOnly) {
|
||||
setMainButtonMobileVisible(isOpen);
|
||||
}
|
||||
};
|
||||
|
||||
const clearAll = () => {
|
||||
if (isRooms) {
|
||||
setIsLoading(true);
|
||||
@ -1346,6 +1425,7 @@ const SectionFilterContent = ({
|
||||
filterTitle={t("Filter")}
|
||||
clearSearch={clearSearch}
|
||||
setClearSearch={setClearSearch}
|
||||
onSortButtonClick={onSortButtonClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1369,6 +1449,7 @@ export default inject(
|
||||
viewAs,
|
||||
createThumbnails,
|
||||
setCurrentRoomsFilter,
|
||||
setMainButtonMobileVisible,
|
||||
thirdPartyStore,
|
||||
clearSearch,
|
||||
setClearSearch,
|
||||
@ -1388,6 +1469,7 @@ export default inject(
|
||||
isRoomsFolder,
|
||||
isArchiveFolder,
|
||||
isPersonalRoom,
|
||||
isTrashFolder: isTrash,
|
||||
} = treeFoldersStore;
|
||||
|
||||
const isRooms = isRoomsFolder || isArchiveFolder;
|
||||
@ -1413,6 +1495,7 @@ export default inject(
|
||||
isFavoritesFolder,
|
||||
isRecentFolder,
|
||||
isRooms,
|
||||
isTrash,
|
||||
|
||||
setIsLoading,
|
||||
fetchFiles,
|
||||
@ -1437,6 +1520,8 @@ export default inject(
|
||||
|
||||
clearSearch,
|
||||
setClearSearch,
|
||||
|
||||
setMainButtonMobileVisible,
|
||||
};
|
||||
}
|
||||
)(
|
||||
|
@ -38,7 +38,7 @@ const StyledComponent = styled.div`
|
||||
}
|
||||
|
||||
.category-item-description {
|
||||
color: #657077;
|
||||
color: ${(props) => props.theme.client.settings.common.descriptionColor};
|
||||
font-size: 13px;
|
||||
max-width: 1024px;
|
||||
line-height: 20px;
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
} from "../../../utils/commonSettingsStyles";
|
||||
import globalColors from "@docspace/components/utils/globalColors";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
import { mobile } from "@docspace/components/utils/device";
|
||||
import { hugeMobile, tablet, mobile } from "@docspace/components/utils/device";
|
||||
|
||||
const linkColor = globalColors.black;
|
||||
|
||||
@ -72,14 +72,34 @@ const StyledManualBackup = styled.div`
|
||||
.manual-backup_buttons {
|
||||
margin-top: 16px;
|
||||
margin-left: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
button:first-child {
|
||||
width: 50%;
|
||||
max-width: 164px;
|
||||
max-width: 124px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
button:last-child {
|
||||
max-width: 164px;
|
||||
width: calc(50% - 8px);
|
||||
max-width: 153px;
|
||||
}
|
||||
|
||||
@media ${tablet} {
|
||||
button:first-child {
|
||||
max-width: 129px;
|
||||
}
|
||||
button:last-child {
|
||||
max-width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
@media ${hugeMobile} {
|
||||
button:first-child {
|
||||
max-width: 155px;
|
||||
}
|
||||
button:last-child {
|
||||
max-width: 155px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.manual-backup_storages-module {
|
||||
|
@ -284,7 +284,7 @@ class ManualBackup extends React.Component {
|
||||
{isCheckedTemporaryStorage && (
|
||||
<div className="manual-backup_buttons">
|
||||
<Button
|
||||
label={t("Common:Duplicate")}
|
||||
label={t("Common:CreateCopy")}
|
||||
onClick={this.onMakeTemporaryBackup}
|
||||
primary
|
||||
isDisabled={!isMaxProgress}
|
||||
|
@ -87,7 +87,7 @@ class RoomsModule extends React.Component {
|
||||
</div>
|
||||
<div className="manual-backup_buttons">
|
||||
<Button
|
||||
label={t("Common:Duplicate")}
|
||||
label={t("Common:CreateCopy")}
|
||||
onClick={this.onMakeCopy}
|
||||
primary
|
||||
isDisabled={isModuleDisabled || !selectedFolder}
|
||||
|
@ -145,7 +145,7 @@ class ThirdPartyModule extends React.Component {
|
||||
|
||||
{connectedThirdPartyAccount?.id && isTheSameThirdPartyAccount && (
|
||||
<Button
|
||||
label={t("Common:Duplicate")}
|
||||
label={t("Common:CreateCopy")}
|
||||
onClick={this.onMakeCopy}
|
||||
primary
|
||||
isDisabled={isModuleDisabled || selectedFolder === ""}
|
||||
|
@ -50,7 +50,7 @@ class AmazonStorage extends React.Component {
|
||||
|
||||
<div className="manual-backup_buttons">
|
||||
<Button
|
||||
label={t("Common:Duplicate")}
|
||||
label={t("Common:CreateCopy")}
|
||||
onClick={onMakeCopyIntoStorage}
|
||||
primary
|
||||
isDisabled={!isValidForm || !isMaxProgress || this.isDisabled}
|
||||
|
@ -47,7 +47,7 @@ class GoogleCloudStorage extends React.Component {
|
||||
|
||||
<div className="manual-backup_buttons">
|
||||
<Button
|
||||
label={t("Common:Duplicate")}
|
||||
label={t("Common:CreateCopy")}
|
||||
onClick={onMakeCopyIntoStorage}
|
||||
primary
|
||||
isDisabled={!isValidForm || !isMaxProgress || this.isDisabled}
|
||||
|
@ -48,7 +48,7 @@ class RackspaceStorage extends React.Component {
|
||||
|
||||
<div className="manual-backup_buttons">
|
||||
<Button
|
||||
label={t("Common:Duplicate")}
|
||||
label={t("Common:CreateCopy")}
|
||||
onClick={onMakeCopyIntoStorage}
|
||||
primary
|
||||
isDisabled={!isValidForm || !isMaxProgress || this.isDisabled}
|
||||
|
@ -47,7 +47,7 @@ class SelectelStorage extends React.Component {
|
||||
|
||||
<div className="manual-backup_buttons">
|
||||
<Button
|
||||
label={t("Common:Duplicate")}
|
||||
label={t("Common:CreateCopy")}
|
||||
onClick={onMakeCopyIntoStorage}
|
||||
primary
|
||||
isDisabled={!isValidForm || !isMaxProgress || this.isDisabled}
|
||||
|
@ -1,29 +1,34 @@
|
||||
import React from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import Button from "@docspace/components/button";
|
||||
import Checkbox from "@docspace/components/checkbox";
|
||||
import Text from "@docspace/components/text";
|
||||
import RadioButton from "@docspace/components/radio-button";
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
import { startRestore } from "@docspace/common/api/portal";
|
||||
import { combineUrl } from "@docspace/common/utils";
|
||||
import { BackupStorageType, TenantStatus } from "@docspace/common/constants";
|
||||
import { request } from "@docspace/common/api/client";
|
||||
import { StyledRestoreBackup } from "./../StyledBackup";
|
||||
import BackupListModalDialog from "./sub-components/backup-list";
|
||||
import RoomsModule from "./sub-components/RoomsModule";
|
||||
import ThirdPartyResources from "./sub-components/ThirdPartyResourcesModule";
|
||||
import ThirdPartyStorages from "./sub-components/ThirdPartyStoragesModule";
|
||||
import LocalFile from "./sub-components/LocalFileModule";
|
||||
import config from "PACKAGE_FILE";
|
||||
|
||||
import { getSettingsThirdParty } from "@docspace/common/api/files";
|
||||
import {
|
||||
getBackupStorage,
|
||||
getStorageRegions,
|
||||
} from "@docspace/common/api/settings";
|
||||
import RestoreBackupLoader from "@docspace/common/components/Loaders/RestoreBackupLoader";
|
||||
import FloatingButton from "@docspace/common/components/FloatingButton";
|
||||
import { getSettingsThirdParty } from "@docspace/common/api/files";
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
import RadioButtonGroup from "@docspace/components/radio-button-group";
|
||||
import { BackupStorageType } from "@docspace/common/constants";
|
||||
import Checkbox from "@docspace/components/checkbox";
|
||||
import Text from "@docspace/components/text";
|
||||
|
||||
import LocalFileModule from "./sub-components/LocalFileModule";
|
||||
import ThirdPartyStoragesModule from "./sub-components/ThirdPartyStoragesModule";
|
||||
import ThirdPartyResourcesModule from "./sub-components/ThirdPartyResourcesModule";
|
||||
import BackupListModalDialog from "./sub-components/backup-list";
|
||||
import RoomsModule from "./sub-components/RoomsModule";
|
||||
import ButtonContainer from "./sub-components/ButtonComponent";
|
||||
import { StyledRestoreBackup } from "../StyledBackup";
|
||||
|
||||
const LOCAL_FILE = "localFile",
|
||||
BACKUP_ROOM = "backupRoom",
|
||||
DISK_SPACE = "thirdPartyDiskSpace",
|
||||
STORAGE_SPACE = "thirdPartyStorageSpace";
|
||||
|
||||
const NOTIFICATION = "notification",
|
||||
CONFIRMATION = "confirmation";
|
||||
|
||||
const {
|
||||
DocumentModuleType,
|
||||
@ -31,47 +36,38 @@ const {
|
||||
StorageModuleType,
|
||||
LocalFileModuleType,
|
||||
} = BackupStorageType;
|
||||
class RestoreBackup extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isChecked: false,
|
||||
isNotify: true,
|
||||
isVisibleDialog: false,
|
||||
isPanelVisible: false,
|
||||
isCheckedDocuments: false,
|
||||
isCheckedThirdParty: false,
|
||||
isCheckedThirdPartyStorage: false,
|
||||
isCheckedLocalFile: true,
|
||||
selectedFileId: "",
|
||||
selectedFile: "",
|
||||
|
||||
isFileSelectedError: false,
|
||||
isInitialLoading: true,
|
||||
checkingRecoveryData: false,
|
||||
};
|
||||
const RestoreBackup = (props) => {
|
||||
const {
|
||||
getProgress,
|
||||
t,
|
||||
setThirdPartyStorage,
|
||||
setStorageRegions,
|
||||
setConnectedThirdPartyAccount,
|
||||
clearProgressInterval,
|
||||
isEnableRestore,
|
||||
setRestoreResource,
|
||||
buttonSize,
|
||||
history,
|
||||
} = props;
|
||||
|
||||
this.switches = [
|
||||
"isCheckedLocalFile",
|
||||
"isCheckedDocuments",
|
||||
"isCheckedThirdParty",
|
||||
"isCheckedThirdPartyStorage",
|
||||
];
|
||||
|
||||
this.storageId = "";
|
||||
}
|
||||
|
||||
setBasicSettings = async () => {
|
||||
const {
|
||||
getProgress,
|
||||
t,
|
||||
setThirdPartyStorage,
|
||||
setStorageRegions,
|
||||
setConnectedThirdPartyAccount,
|
||||
} = this.props;
|
||||
const [radioButtonState, setRadioButtonState] = useState(LOCAL_FILE);
|
||||
const [checkboxState, setCheckboxState] = useState({
|
||||
notification: true,
|
||||
confirmation: false,
|
||||
});
|
||||
const [isInitialLoading, setIsInitialLoading] = useState(true);
|
||||
const [isVisibleBackupListDialog, setIsVisibleBackupListDialog] = useState(
|
||||
false
|
||||
);
|
||||
const [isVisibleSelectFileDialog, setIsVisibleSelectFileDialog] = useState(
|
||||
false
|
||||
);
|
||||
|
||||
useEffect(async () => {
|
||||
try {
|
||||
getProgress(t);
|
||||
|
||||
const [account, backupStorage, storageRegions] = await Promise.all([
|
||||
getSettingsThirdParty(),
|
||||
getBackupStorage(),
|
||||
@ -82,439 +78,223 @@ class RestoreBackup extends React.Component {
|
||||
setThirdPartyStorage(backupStorage);
|
||||
setStorageRegions(storageRegions);
|
||||
|
||||
this.setState({
|
||||
isInitialLoading: false,
|
||||
});
|
||||
setIsInitialLoading(false);
|
||||
} catch (error) {
|
||||
toastr.error(error);
|
||||
|
||||
this.setState({
|
||||
isInitialLoading: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.setBasicSettings();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { clearProgressInterval } = this.props;
|
||||
clearProgressInterval();
|
||||
}
|
||||
onChangeCheckbox = () => {
|
||||
this.setState({
|
||||
isChecked: !this.state.isChecked,
|
||||
});
|
||||
};
|
||||
onChangeCheckboxNotify = () => {
|
||||
this.setState({
|
||||
isNotify: !this.state.isNotify,
|
||||
});
|
||||
};
|
||||
onClickBackupList = () => {
|
||||
this.setState({
|
||||
isVisibleDialog: !this.state.isVisibleDialog,
|
||||
});
|
||||
};
|
||||
onModalClose = () => {
|
||||
this.setState({
|
||||
isVisibleDialog: false,
|
||||
});
|
||||
};
|
||||
onClickInput = () => {
|
||||
this.setState({
|
||||
isPanelVisible: true,
|
||||
});
|
||||
};
|
||||
onPanelClose = () => {
|
||||
this.setState({
|
||||
isPanelVisible: false,
|
||||
});
|
||||
};
|
||||
onClickShowStorage = (e) => {
|
||||
let newStateObj = {};
|
||||
const name = e.target.name;
|
||||
newStateObj[name] = true;
|
||||
const newState = this.switches.filter((el) => el !== name);
|
||||
newState.forEach((name) => (newStateObj[name] = false));
|
||||
this.setState({
|
||||
...newStateObj,
|
||||
});
|
||||
};
|
||||
onSelectFile = (file) => {
|
||||
this.setState({
|
||||
selectedFileId: file.id,
|
||||
});
|
||||
};
|
||||
onSelectLocalFile = (data) => {
|
||||
this.setState({
|
||||
selectedFile: data,
|
||||
});
|
||||
};
|
||||
canRestore = () => {
|
||||
const {
|
||||
isCheckedDocuments,
|
||||
isCheckedLocalFile,
|
||||
selectedFileId,
|
||||
selectedFile,
|
||||
isCheckedThirdPartyStorage,
|
||||
isCheckedThirdParty,
|
||||
} = this.state;
|
||||
|
||||
const { isFormReady } = this.props;
|
||||
|
||||
if (isCheckedDocuments || isCheckedThirdParty) {
|
||||
if (!selectedFileId) return false;
|
||||
return true;
|
||||
}
|
||||
if (isCheckedLocalFile) {
|
||||
if (!selectedFile) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isCheckedThirdPartyStorage) {
|
||||
return isFormReady();
|
||||
}
|
||||
};
|
||||
onRestoreClick = async () => {
|
||||
const {
|
||||
isNotify,
|
||||
isCheckedDocuments,
|
||||
isCheckedLocalFile,
|
||||
selectedFileId,
|
||||
selectedFile,
|
||||
isCheckedThirdPartyStorage,
|
||||
isCheckedThirdParty,
|
||||
} = this.state;
|
||||
const {
|
||||
history,
|
||||
socketHelper,
|
||||
getStorageParams,
|
||||
setTenantStatus,
|
||||
} = this.props;
|
||||
|
||||
if (!this.canRestore()) {
|
||||
this.setState({
|
||||
isFileSelectedError: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
checkingRecoveryData: true,
|
||||
isFileSelectedError: false,
|
||||
});
|
||||
let storageParams = [];
|
||||
let obj = {};
|
||||
const backupId = "";
|
||||
const storageType = isCheckedDocuments
|
||||
? `${DocumentModuleType}`
|
||||
: isCheckedThirdParty
|
||||
? `${ResourcesModuleType}`
|
||||
: isCheckedLocalFile
|
||||
? `${LocalFileModuleType}`
|
||||
: `${StorageModuleType}`;
|
||||
|
||||
if (isCheckedThirdPartyStorage) {
|
||||
storageParams = getStorageParams(true, null, this.storageId);
|
||||
} else {
|
||||
obj.key = "filePath";
|
||||
if (isCheckedDocuments || isCheckedThirdParty) {
|
||||
obj.value = selectedFileId;
|
||||
} else {
|
||||
obj.value = "";
|
||||
}
|
||||
storageParams.push(obj);
|
||||
}
|
||||
let checkedFile;
|
||||
try {
|
||||
if (isCheckedLocalFile) {
|
||||
checkedFile = await request({
|
||||
baseURL: combineUrl(
|
||||
window.DocSpaceConfig?.proxy?.url,
|
||||
config.homepage
|
||||
),
|
||||
method: "post",
|
||||
url: `/backupFileUpload.ashx`,
|
||||
responseType: "text",
|
||||
data: selectedFile,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
toastr.error(e);
|
||||
this.setState({
|
||||
checkingRecoveryData: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (isCheckedLocalFile && checkedFile?.Message) {
|
||||
toastr.error(checkedFile.Message);
|
||||
this.setState({
|
||||
checkingRecoveryData: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
startRestore(backupId, storageType, storageParams, isNotify)
|
||||
.then(() => setTenantStatus(TenantStatus.PortalRestore))
|
||||
.then(() => {
|
||||
socketHelper.emit({
|
||||
command: "restore-backup",
|
||||
});
|
||||
})
|
||||
.then(() => this.setState({ checkingRecoveryData: false }))
|
||||
.then(() =>
|
||||
history.push(
|
||||
combineUrl(
|
||||
window.DocSpaceConfig?.proxy?.url,
|
||||
config.homepage,
|
||||
"/preparation-portal"
|
||||
)
|
||||
)
|
||||
)
|
||||
.catch((error) => {
|
||||
toastr.error(error);
|
||||
this.setState({
|
||||
checkingRecoveryData: false,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onSetStorageId = (storageId) => {
|
||||
this.storageId = storageId;
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
history,
|
||||
downloadingProgress,
|
||||
buttonSize,
|
||||
theme,
|
||||
isEnableRestore,
|
||||
} = this.props;
|
||||
const {
|
||||
isChecked,
|
||||
isInitialLoading,
|
||||
isNotify,
|
||||
isVisibleDialog,
|
||||
isPanelVisible,
|
||||
isCheckedDocuments,
|
||||
isCheckedThirdParty,
|
||||
isCheckedThirdPartyStorage,
|
||||
isCheckedLocalFile,
|
||||
checkingRecoveryData,
|
||||
isFileSelectedError,
|
||||
} = this.state;
|
||||
|
||||
const commonRadioButtonProps = {
|
||||
fontSize: "13px",
|
||||
fontWeight: "400",
|
||||
value: "value",
|
||||
className: "backup_radio-button",
|
||||
onClick: this.onClickShowStorage,
|
||||
return () => {
|
||||
clearProgressInterval();
|
||||
setRestoreResource(null);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onClickVersionListProp = isEnableRestore
|
||||
? { onClick: this.onClickBackupList }
|
||||
: {};
|
||||
const onChangeRadioButton = (e) => {
|
||||
const value = e.target.value;
|
||||
if (value === radioButtonState) return;
|
||||
|
||||
const isMaxProgress = downloadingProgress === 100;
|
||||
setRestoreResource(null);
|
||||
setRadioButtonState(value);
|
||||
};
|
||||
|
||||
return isInitialLoading ? (
|
||||
<RestoreBackupLoader />
|
||||
) : (
|
||||
<StyledRestoreBackup theme={theme} isEnableRestore={isEnableRestore}>
|
||||
<div className="restore-description">
|
||||
<Text className="restore-description settings_unavailable">
|
||||
{t("RestoreBackupDescription")}
|
||||
</Text>
|
||||
</div>
|
||||
const onChangeCheckbox = (e) => {
|
||||
const name = e.target.name;
|
||||
const checked = e.target.checked;
|
||||
|
||||
<RadioButton
|
||||
label={t("LocalFile")}
|
||||
name={"isCheckedLocalFile"}
|
||||
key={4}
|
||||
isChecked={isCheckedLocalFile}
|
||||
setCheckboxState({ ...checkboxState, [name]: checked });
|
||||
};
|
||||
|
||||
const getStorageType = () => {
|
||||
switch (radioButtonState) {
|
||||
case LOCAL_FILE:
|
||||
return LocalFileModuleType;
|
||||
case BACKUP_ROOM:
|
||||
return DocumentModuleType;
|
||||
case DISK_SPACE:
|
||||
return ResourcesModuleType;
|
||||
case STORAGE_SPACE:
|
||||
return StorageModuleType;
|
||||
}
|
||||
};
|
||||
|
||||
const onClickBackupList = () => {
|
||||
setIsVisibleBackupListDialog(true);
|
||||
};
|
||||
|
||||
const onClickInput = () => {
|
||||
setIsVisibleSelectFileDialog(true);
|
||||
};
|
||||
const onModalClose = () => {
|
||||
setIsVisibleBackupListDialog(false);
|
||||
setIsVisibleSelectFileDialog(false);
|
||||
};
|
||||
|
||||
const onSetStorageId = (id) => {
|
||||
setRestoreResource(id);
|
||||
};
|
||||
|
||||
const radioButtonContent = (
|
||||
<>
|
||||
<RadioButtonGroup
|
||||
name="restore_backup"
|
||||
orientation="vertical"
|
||||
fontSize="13px"
|
||||
fontWeight="400"
|
||||
className="backup_radio-button"
|
||||
options={[
|
||||
{ value: LOCAL_FILE, label: t("LocalFile") },
|
||||
{ value: BACKUP_ROOM, label: t("RoomsModule") },
|
||||
{ value: DISK_SPACE, label: t("ThirdPartyResource") },
|
||||
{ value: STORAGE_SPACE, label: t("ThirdPartyStorage") },
|
||||
]}
|
||||
onClick={onChangeRadioButton}
|
||||
selected={radioButtonState}
|
||||
spacing="16px"
|
||||
isDisabled={!isEnableRestore}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
const backupModules = (
|
||||
<div className="restore-backup_modules">
|
||||
{radioButtonState === LOCAL_FILE && <LocalFileModule t={t} />}
|
||||
|
||||
{radioButtonState === BACKUP_ROOM && (
|
||||
<RoomsModule
|
||||
isDisabled={!isEnableRestore}
|
||||
{...commonRadioButtonProps}
|
||||
t={t}
|
||||
isPanelVisible={isVisibleSelectFileDialog}
|
||||
onClose={onModalClose}
|
||||
onClickInput={onClickInput}
|
||||
onSelectFile={(file) => setRestoreResource(file.id)}
|
||||
/>
|
||||
|
||||
<RadioButton
|
||||
label={t("RoomsModule")}
|
||||
name={"isCheckedDocuments"}
|
||||
key={1}
|
||||
isChecked={isCheckedDocuments}
|
||||
isDisabled={!isEnableRestore}
|
||||
{...commonRadioButtonProps}
|
||||
)}
|
||||
{radioButtonState === DISK_SPACE && (
|
||||
<ThirdPartyResourcesModule
|
||||
t={t}
|
||||
isPanelVisible={isVisibleSelectFileDialog}
|
||||
onClose={onModalClose}
|
||||
onClickInput={onClickInput}
|
||||
onSelectFile={(file) => setRestoreResource(file.id)}
|
||||
buttonSize={buttonSize}
|
||||
/>
|
||||
)}
|
||||
{radioButtonState === STORAGE_SPACE && (
|
||||
<ThirdPartyStoragesModule onSetStorageId={onSetStorageId} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
<RadioButton
|
||||
label={t("ThirdPartyResource")}
|
||||
name={"isCheckedThirdParty"}
|
||||
key={2}
|
||||
isChecked={isCheckedThirdParty}
|
||||
isDisabled={!isEnableRestore}
|
||||
{...commonRadioButtonProps}
|
||||
/>
|
||||
const warningContent = (
|
||||
<>
|
||||
<Text className="restore-backup_warning settings_unavailable" noSelect>
|
||||
{t("Common:Warning")}
|
||||
{"!"}
|
||||
</Text>
|
||||
<Text
|
||||
className="restore-backup_warning-description settings_unavailable"
|
||||
noSelect
|
||||
>
|
||||
{t("RestoreBackupWarningText")}
|
||||
</Text>
|
||||
<Text
|
||||
className="restore-backup_warning-link settings_unavailable"
|
||||
noSelect
|
||||
>
|
||||
{t("RestoreBackupResetInfoWarningText")}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
|
||||
<RadioButton
|
||||
label={t("ThirdPartyStorage")}
|
||||
name={"isCheckedThirdPartyStorage"}
|
||||
key={3}
|
||||
isChecked={isCheckedThirdPartyStorage}
|
||||
isDisabled={!isEnableRestore}
|
||||
{...commonRadioButtonProps}
|
||||
/>
|
||||
const onClickVersionListProp = isEnableRestore
|
||||
? { onClick: onClickBackupList }
|
||||
: {};
|
||||
|
||||
<div className="restore-backup_modules">
|
||||
{isCheckedDocuments && (
|
||||
<RoomsModule
|
||||
isDisabled={!isEnableRestore}
|
||||
t={t}
|
||||
isPanelVisible={isPanelVisible}
|
||||
onClose={this.onPanelClose}
|
||||
onClickInput={this.onClickInput}
|
||||
onSelectFile={this.onSelectFile}
|
||||
isError={isFileSelectedError}
|
||||
/>
|
||||
)}
|
||||
{isCheckedThirdParty && (
|
||||
<ThirdPartyResources
|
||||
t={t}
|
||||
isPanelVisible={isPanelVisible}
|
||||
onClose={this.onPanelClose}
|
||||
onClickInput={this.onClickInput}
|
||||
onSelectFile={this.onSelectFile}
|
||||
isError={isFileSelectedError}
|
||||
buttonSize={buttonSize}
|
||||
/>
|
||||
)}
|
||||
{isCheckedThirdPartyStorage && (
|
||||
<ThirdPartyStorages
|
||||
onResetFormSettings={this.onResetFormSettings}
|
||||
onSetStorageId={this.onSetStorageId}
|
||||
/>
|
||||
)}
|
||||
{isCheckedLocalFile && (
|
||||
<LocalFile
|
||||
hasError={isFileSelectedError}
|
||||
onSelectLocalFile={this.onSelectLocalFile}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Text
|
||||
className="restore-backup_list settings_unavailable"
|
||||
{...onClickVersionListProp}
|
||||
noSelect
|
||||
>
|
||||
{t("BackupList")}
|
||||
if (isInitialLoading) return <RestoreBackupLoader />;
|
||||
console.log("index render");
|
||||
return (
|
||||
<StyledRestoreBackup isEnableRestore={isEnableRestore}>
|
||||
<div className="restore-description">
|
||||
<Text className="restore-description settings_unavailable">
|
||||
{t("RestoreBackupDescription")}
|
||||
</Text>
|
||||
</div>
|
||||
{radioButtonContent}
|
||||
{backupModules}
|
||||
|
||||
{isVisibleDialog && (
|
||||
<BackupListModalDialog
|
||||
isVisibleDialog={isVisibleDialog}
|
||||
onModalClose={this.onModalClose}
|
||||
isNotify={isNotify}
|
||||
isCopyingToLocal={!isMaxProgress}
|
||||
history={history}
|
||||
/>
|
||||
)}
|
||||
<Checkbox
|
||||
truncate
|
||||
className="restore-backup-checkbox_notification"
|
||||
onChange={this.onChangeCheckboxNotify}
|
||||
isChecked={isNotify}
|
||||
label={t("SendNotificationAboutRestoring")}
|
||||
isDisabled={!isEnableRestore}
|
||||
<Text
|
||||
className="restore-backup_list settings_unavailable"
|
||||
{...onClickVersionListProp}
|
||||
noSelect
|
||||
>
|
||||
{t("BackupList")}
|
||||
</Text>
|
||||
|
||||
{isVisibleBackupListDialog && (
|
||||
<BackupListModalDialog
|
||||
isVisibleDialog={isVisibleBackupListDialog}
|
||||
onModalClose={onModalClose}
|
||||
isNotify={checkboxState.notification}
|
||||
history={history}
|
||||
/>
|
||||
|
||||
<Text className="restore-backup_warning settings_unavailable" noSelect>
|
||||
{t("Common:Warning")}
|
||||
{"!"}
|
||||
</Text>
|
||||
<Text
|
||||
className="restore-backup_warning-description settings_unavailable"
|
||||
noSelect
|
||||
>
|
||||
{t("RestoreBackupWarningText")}
|
||||
</Text>
|
||||
<Text
|
||||
className="restore-backup_warning-link settings_unavailable"
|
||||
noSelect
|
||||
>
|
||||
{t("RestoreBackupResetInfoWarningText")}
|
||||
</Text>
|
||||
|
||||
<Checkbox
|
||||
truncate
|
||||
className="restore-backup-checkbox"
|
||||
onChange={this.onChangeCheckbox}
|
||||
isChecked={isChecked}
|
||||
label={t("UserAgreement")}
|
||||
isDisabled={!isEnableRestore}
|
||||
/>
|
||||
|
||||
<Button
|
||||
className="restore-backup_button"
|
||||
label={t("Common:Restore")}
|
||||
onClick={this.onRestoreClick}
|
||||
primary
|
||||
isDisabled={
|
||||
checkingRecoveryData ||
|
||||
!isMaxProgress ||
|
||||
!isChecked ||
|
||||
!isEnableRestore
|
||||
}
|
||||
isLoading={checkingRecoveryData}
|
||||
size={buttonSize}
|
||||
tabIndex={10}
|
||||
/>
|
||||
|
||||
{downloadingProgress > 0 && downloadingProgress !== 100 && (
|
||||
<FloatingButton
|
||||
className="layout-progress-bar"
|
||||
icon="file"
|
||||
alert={false}
|
||||
percent={downloadingProgress}
|
||||
/>
|
||||
)}
|
||||
</StyledRestoreBackup>
|
||||
);
|
||||
}
|
||||
}
|
||||
)}
|
||||
<Checkbox
|
||||
truncate
|
||||
name={NOTIFICATION}
|
||||
className="restore-backup-checkbox_notification"
|
||||
onChange={onChangeCheckbox}
|
||||
isChecked={checkboxState.notification}
|
||||
label={t("SendNotificationAboutRestoring")}
|
||||
isDisabled={!isEnableRestore}
|
||||
/>
|
||||
{warningContent}
|
||||
<Checkbox
|
||||
truncate
|
||||
name={CONFIRMATION}
|
||||
className="restore-backup-checkbox"
|
||||
onChange={onChangeCheckbox}
|
||||
isChecked={checkboxState.confirmation}
|
||||
label={t("UserAgreement")}
|
||||
isDisabled={!isEnableRestore}
|
||||
/>
|
||||
<ButtonContainer
|
||||
isConfirmed={checkboxState.confirmation}
|
||||
isNotification={checkboxState.notification}
|
||||
getStorageType={getStorageType}
|
||||
radioButtonState={radioButtonState}
|
||||
isCheckedThirdPartyStorage={radioButtonState === STORAGE_SPACE}
|
||||
isCheckedLocalFile={radioButtonState === LOCAL_FILE}
|
||||
history={history}
|
||||
t={t}
|
||||
buttonSize={buttonSize}
|
||||
/>
|
||||
</StyledRestoreBackup>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject(({ auth, backup }) => {
|
||||
const { settingsStore, currentQuotaStore } = auth;
|
||||
const { socketHelper, theme, isTabletView, setTenantStatus } = settingsStore;
|
||||
const { isTabletView } = settingsStore;
|
||||
const { isRestoreAndAutoBackupAvailable } = currentQuotaStore;
|
||||
const {
|
||||
downloadingProgress,
|
||||
getProgress,
|
||||
clearProgressInterval,
|
||||
setStorageRegions,
|
||||
setThirdPartyStorage,
|
||||
isFormReady,
|
||||
getStorageParams,
|
||||
setConnectedThirdPartyAccount,
|
||||
setRestoreResource,
|
||||
} = backup;
|
||||
|
||||
const buttonSize = isTabletView ? "normal" : "small";
|
||||
const { isRestoreAndAutoBackupAvailable } = currentQuotaStore;
|
||||
|
||||
return {
|
||||
setTenantStatus,
|
||||
isEnableRestore: isRestoreAndAutoBackupAvailable,
|
||||
setStorageRegions,
|
||||
setThirdPartyStorage,
|
||||
buttonSize,
|
||||
setConnectedThirdPartyAccount,
|
||||
theme,
|
||||
clearProgressInterval,
|
||||
downloadingProgress,
|
||||
socketHelper,
|
||||
isFormReady,
|
||||
|
||||
getProgress,
|
||||
getStorageParams,
|
||||
setRestoreResource,
|
||||
};
|
||||
})(withTranslation(["Settings", "Common"])(observer(RestoreBackup)));
|
||||
|
@ -0,0 +1,171 @@
|
||||
import React, { useState } from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import config from "PACKAGE_FILE";
|
||||
|
||||
import Button from "@docspace/components/button";
|
||||
import FloatingButton from "@docspace/common/components/FloatingButton";
|
||||
import { TenantStatus } from "@docspace/common/constants";
|
||||
import { startRestore } from "@docspace/common/api/portal";
|
||||
import { combineUrl } from "@docspace/common/utils";
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
import { request } from "@docspace/common/api/client";
|
||||
|
||||
const ButtonContainer = (props) => {
|
||||
const {
|
||||
downloadingProgress,
|
||||
isMaxProgress,
|
||||
isConfirmed,
|
||||
getStorageType,
|
||||
isNotification,
|
||||
restoreResource,
|
||||
isCheckedThirdPartyStorage,
|
||||
isCheckedLocalFile,
|
||||
history,
|
||||
isEnableRestore,
|
||||
t,
|
||||
buttonSize,
|
||||
socketHelper,
|
||||
setTenantStatus,
|
||||
isFormReady,
|
||||
getStorageParams,
|
||||
} = props;
|
||||
|
||||
const [isUploadingLocalFile, setIsUploadingLocalFile] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const localFileUploading = async () => {
|
||||
try {
|
||||
const checkedFile = await request({
|
||||
baseURL: combineUrl(window.DocSpaceConfig?.proxy?.url, config.homepage),
|
||||
method: "post",
|
||||
url: `/backupFileUpload.ashx`,
|
||||
responseType: "text",
|
||||
data: restoreResource,
|
||||
});
|
||||
|
||||
return checkedFile;
|
||||
} catch (e) {
|
||||
toastr.error(e);
|
||||
setIsUploadingLocalFile(false);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const onRestoreClick = async () => {
|
||||
if (isCheckedThirdPartyStorage) {
|
||||
const requiredFieldsFilled = isFormReady();
|
||||
if (!requiredFieldsFilled) return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
|
||||
let storageParams = [],
|
||||
tempObj = {};
|
||||
|
||||
const backupId = "";
|
||||
const storageType = getStorageType().toString();
|
||||
|
||||
if (isCheckedThirdPartyStorage) {
|
||||
storageParams = getStorageParams(true, null, restoreResource);
|
||||
} else {
|
||||
tempObj.key = "filePath";
|
||||
tempObj.value = isCheckedLocalFile ? "" : restoreResource;
|
||||
|
||||
storageParams.push(tempObj);
|
||||
}
|
||||
|
||||
if (isCheckedLocalFile) {
|
||||
const isUploadedFile = await localFileUploading();
|
||||
|
||||
if (!isUploadedFile) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isUploadedFile?.Message) {
|
||||
toastr.error(isUploadedFile.Message);
|
||||
setIsUploadingLocalFile(false);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await startRestore(backupId, storageType, storageParams, isNotification);
|
||||
setTenantStatus(TenantStatus.PortalRestore);
|
||||
|
||||
socketHelper.emit({
|
||||
command: "restore-backup",
|
||||
});
|
||||
|
||||
history.push(
|
||||
combineUrl(
|
||||
window.DocSpaceConfig?.proxy?.url,
|
||||
config.homepage,
|
||||
"/preparation-portal"
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
toastr.error(e);
|
||||
|
||||
setIsUploadingLocalFile(false);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const isButtonDisabled =
|
||||
isLoading ||
|
||||
isUploadingLocalFile ||
|
||||
!isMaxProgress ||
|
||||
!isConfirmed ||
|
||||
!isEnableRestore ||
|
||||
!restoreResource;
|
||||
const isLoadingButton = isUploadingLocalFile || isLoading;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
className="restore-backup_button"
|
||||
label={t("Common:Restore")}
|
||||
onClick={onRestoreClick}
|
||||
primary
|
||||
isDisabled={isButtonDisabled}
|
||||
isLoading={isLoadingButton}
|
||||
size={buttonSize}
|
||||
tabIndex={10}
|
||||
/>
|
||||
|
||||
{downloadingProgress > 0 && !isMaxProgress && (
|
||||
<FloatingButton
|
||||
className="layout-progress-bar"
|
||||
icon="file"
|
||||
alert={false}
|
||||
percent={downloadingProgress}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject(({ auth, backup }) => {
|
||||
const { settingsStore, currentQuotaStore } = auth;
|
||||
const { socketHelper, setTenantStatus } = settingsStore;
|
||||
const {
|
||||
downloadingProgress,
|
||||
isFormReady,
|
||||
getStorageParams,
|
||||
restoreResource,
|
||||
} = backup;
|
||||
|
||||
const { isRestoreAndAutoBackupAvailable } = currentQuotaStore;
|
||||
const isMaxProgress = downloadingProgress === 100;
|
||||
return {
|
||||
isMaxProgress,
|
||||
setTenantStatus,
|
||||
isEnableRestore: isRestoreAndAutoBackupAvailable,
|
||||
downloadingProgress,
|
||||
socketHelper,
|
||||
isFormReady,
|
||||
getStorageParams,
|
||||
restoreResource,
|
||||
};
|
||||
})(observer(ButtonContainer));
|
@ -1,27 +1,33 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
|
||||
import FileInput from "@docspace/components/file-input";
|
||||
|
||||
const LocalFile = ({ onSelectLocalFile, hasError }) => {
|
||||
const LocalFile = ({ setRestoreResource, isEnableRestore, t }) => {
|
||||
const onClickInput = (file) => {
|
||||
let data = new FormData();
|
||||
data.append("file", file);
|
||||
|
||||
onSelectLocalFile(data);
|
||||
setRestoreResource(data);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
onSelectLocalFile("");
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<FileInput
|
||||
hasError={hasError}
|
||||
onInput={onClickInput}
|
||||
scale
|
||||
className="restore-backup_input"
|
||||
isDisabled={!isEnableRestore}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default LocalFile;
|
||||
export default inject(({ auth, backup }) => {
|
||||
const { currentQuotaStore } = auth;
|
||||
const { setRestoreResource } = backup;
|
||||
const { isRestoreAndAutoBackupAvailable } = currentQuotaStore;
|
||||
|
||||
return {
|
||||
isEnableRestore: isRestoreAndAutoBackupAvailable,
|
||||
setRestoreResource,
|
||||
};
|
||||
})(observer(LocalFile));
|
||||
|
@ -302,14 +302,17 @@ BackupListModalDialog.propTypes = {
|
||||
isVisibleDialog: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default inject(({ auth }) => {
|
||||
export default inject(({ auth, backup }) => {
|
||||
const { settingsStore } = auth;
|
||||
const { downloadingProgress } = backup;
|
||||
const { socketHelper, theme, setTenantStatus } = settingsStore;
|
||||
const isCopyingToLocal = downloadingProgress !== 100;
|
||||
|
||||
return {
|
||||
setTenantStatus,
|
||||
theme,
|
||||
socketHelper,
|
||||
isCopyingToLocal,
|
||||
};
|
||||
})(
|
||||
withTranslation(["Settings", "Common", "Translations"])(
|
||||
|
@ -12,6 +12,8 @@ import { AutoBackupPeriod } from "@docspace/common/constants";
|
||||
const { EveryDayType, EveryWeekType } = AutoBackupPeriod;
|
||||
|
||||
class BackupStore {
|
||||
restoreResource = null;
|
||||
|
||||
backupSchedule = {};
|
||||
backupStorage = {};
|
||||
|
||||
@ -343,7 +345,8 @@ class BackupStore {
|
||||
};
|
||||
|
||||
clearLocalStorage = () => {
|
||||
removeLocalStorage("LocalCopyStorageType");
|
||||
getFromLocalStorage("LocalCopyStorageType") &&
|
||||
removeLocalStorage("LocalCopyStorageType");
|
||||
|
||||
getFromLocalStorage("LocalCopyFolder") &&
|
||||
removeLocalStorage("LocalCopyFolder");
|
||||
@ -610,6 +613,10 @@ class BackupStore {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
setRestoreResource = (value) => {
|
||||
this.restoreResource = value;
|
||||
};
|
||||
}
|
||||
|
||||
export default BackupStore;
|
||||
|
@ -101,6 +101,7 @@ class FilesStore {
|
||||
pageItemsLength = null;
|
||||
isHidePagination = false;
|
||||
trashIsEmpty = false;
|
||||
mainButtonMobileVisible = true;
|
||||
filesIsLoading = false;
|
||||
|
||||
isEmptyPage = false;
|
||||
@ -3088,6 +3089,10 @@ class FilesStore {
|
||||
this.trashIsEmpty = isEmpty;
|
||||
};
|
||||
|
||||
setMainButtonMobileVisible = (visible) => {
|
||||
this.mainButtonMobileVisible = visible;
|
||||
};
|
||||
|
||||
get roomsFilterTotal() {
|
||||
return this.roomsFilter.total;
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { makeAutoObservable } from "mobx";
|
||||
import { makeAutoObservable, runInAction } from "mobx";
|
||||
import {
|
||||
isNullOrUndefined,
|
||||
findNearestIndex,
|
||||
} from "@docspace/common/components/MediaViewer/helpers";
|
||||
|
||||
class MediaViewerDataStore {
|
||||
filesStore;
|
||||
@ -8,6 +12,7 @@ class MediaViewerDataStore {
|
||||
visible = false;
|
||||
previewFile = null;
|
||||
currentItem = null;
|
||||
prevPostionIndex = 0;
|
||||
|
||||
constructor(filesStore, settingsStore) {
|
||||
makeAutoObservable(this);
|
||||
@ -45,6 +50,68 @@ class MediaViewerDataStore {
|
||||
this.id = id;
|
||||
};
|
||||
|
||||
changeUrl = (id) => {
|
||||
const url = "/products/files/#preview/" + id;
|
||||
window.history.pushState(null, null, url);
|
||||
};
|
||||
|
||||
nextMedia = () => {
|
||||
const { setBufferSelection, files } = this.filesStore;
|
||||
|
||||
const postionIndex = (this.currentPostionIndex + 1) % this.playlist.length;
|
||||
|
||||
if (postionIndex === 0) {
|
||||
return;
|
||||
}
|
||||
const currentFileId = this.playlist[postionIndex].fileId;
|
||||
|
||||
const targetFile = files.find((item) => item.id === currentFileId);
|
||||
|
||||
if (!isNullOrUndefined(targetFile)) setBufferSelection(targetFile);
|
||||
|
||||
const fileId = this.playlist[postionIndex].fileId;
|
||||
this.setCurrentId(fileId);
|
||||
this.changeUrl(fileId);
|
||||
};
|
||||
|
||||
prevMedia = () => {
|
||||
const { setBufferSelection, files } = this.filesStore;
|
||||
|
||||
let currentPlaylistPos = this.currentPostionIndex - 1;
|
||||
|
||||
if (currentPlaylistPos === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentFileId = this.playlist[currentPlaylistPos].fileId;
|
||||
|
||||
const targetFile = files.find((item) => item.id === currentFileId);
|
||||
|
||||
if (!isNullOrUndefined(targetFile)) setBufferSelection(targetFile);
|
||||
|
||||
const fileId = this.playlist[currentPlaylistPos].fileId;
|
||||
this.setCurrentId(fileId);
|
||||
this.changeUrl(fileId);
|
||||
};
|
||||
|
||||
get currentPostionIndex() {
|
||||
if (this.playlist.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let index = this.playlist.find((file) => file.fileId === this.id)?.id;
|
||||
|
||||
if (isNullOrUndefined(index)) {
|
||||
index = findNearestIndex(this.playlist, this.prevPostionIndex);
|
||||
}
|
||||
|
||||
runInAction(() => {
|
||||
this.prevPostionIndex = index;
|
||||
});
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
get playlist() {
|
||||
const { files } = this.filesStore;
|
||||
|
||||
@ -83,6 +150,11 @@ class MediaViewerDataStore {
|
||||
id++;
|
||||
}
|
||||
});
|
||||
if (this.previewFile) {
|
||||
runInAction(() => {
|
||||
this.previewFile = null;
|
||||
});
|
||||
}
|
||||
} else if (this.previewFile) {
|
||||
playlist.push({
|
||||
...this.previewFile,
|
||||
|
@ -3,12 +3,15 @@ import { TableVersions } from "SRC_DIR/helpers/constants";
|
||||
|
||||
const TABLE_COLUMNS = `filesTableColumns_ver-${TableVersions.Files}`;
|
||||
const TABLE_ROOMS_COLUMNS = `roomsTableColumns_ver-${TableVersions.Rooms}`;
|
||||
const TABLE_TRASH_COLUMNS = `trashTableColumns_ver-${TableVersions.Trash}`;
|
||||
|
||||
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_SIZE_INFO_PANEL = `filesColumnsSizeInfoPanel_ver-${TableVersions.Files}`;
|
||||
const COLUMNS_ROOMS_SIZE_INFO_PANEL = `roomsColumnsSizeInfoPanel_ver-${TableVersions.Rooms}`;
|
||||
const COLUMNS_TRASH_SIZE_INFO_PANEL = `trashColumnsSizeInfoPanel_ver-${TableVersions.Trash}`;
|
||||
|
||||
class TableStore {
|
||||
authStore;
|
||||
@ -22,6 +25,7 @@ class TableStore {
|
||||
|
||||
nameColumnIsEnabled = true; // always true
|
||||
authorColumnIsEnabled = false;
|
||||
erasureColumnIsEnabled = true;
|
||||
createdColumnIsEnabled = true;
|
||||
modifiedColumnIsEnabled = true;
|
||||
sizeColumnIsEnabled = true;
|
||||
@ -54,18 +58,27 @@ class TableStore {
|
||||
setAuthorColumn = (enable) => {
|
||||
this.authorColumnIsEnabled = enable;
|
||||
};
|
||||
|
||||
setCreatedColumn = (enable) => {
|
||||
this.createdColumnIsEnabled = enable;
|
||||
};
|
||||
|
||||
setModifiedColumn = (enable) => {
|
||||
this.modifiedColumnIsEnabled = enable;
|
||||
};
|
||||
|
||||
setErasureColumn = (enable) => {
|
||||
this.erasureColumnIsEnabled = enable;
|
||||
};
|
||||
|
||||
setSizeColumn = (enable) => {
|
||||
this.sizeColumnIsEnabled = enable;
|
||||
};
|
||||
|
||||
setTypeColumn = (enable) => {
|
||||
this.typeColumnIsEnabled = enable;
|
||||
};
|
||||
|
||||
setQuickButtonsColumn = (enable) => {
|
||||
this.quickButtonsColumnIsEnabled = enable;
|
||||
};
|
||||
@ -75,7 +88,11 @@ class TableStore {
|
||||
const splitColumns = storageColumns && storageColumns.split(",");
|
||||
|
||||
if (splitColumns) {
|
||||
const { isRoomsFolder, isArchiveFolder } = this.treeFoldersStore;
|
||||
const {
|
||||
isRoomsFolder,
|
||||
isArchiveFolder,
|
||||
isTrashFolder,
|
||||
} = this.treeFoldersStore;
|
||||
const isRooms = isRoomsFolder || isArchiveFolder;
|
||||
|
||||
if (isRooms) {
|
||||
@ -83,14 +100,25 @@ class TableStore {
|
||||
this.setRoomColumnTags(splitColumns.includes("Tags"));
|
||||
this.setRoomColumnOwner(splitColumns.includes("Owner"));
|
||||
this.setRoomColumnActivity(splitColumns.includes("Activity"));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTrashFolder) {
|
||||
this.setAuthorColumn(splitColumns.includes("Author"));
|
||||
this.setCreatedColumn(splitColumns.includes("Created"));
|
||||
this.setModifiedColumn(splitColumns.includes("Modified"));
|
||||
this.setErasureColumn(splitColumns.includes("Erasure"));
|
||||
this.setSizeColumn(splitColumns.includes("Size"));
|
||||
this.setTypeColumn(splitColumns.includes("Type"));
|
||||
this.setQuickButtonsColumn(splitColumns.includes("QuickButtons"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.setModifiedColumn(splitColumns.includes("Modified"));
|
||||
this.setAuthorColumn(splitColumns.includes("Author"));
|
||||
this.setCreatedColumn(splitColumns.includes("Created"));
|
||||
this.setSizeColumn(splitColumns.includes("Size"));
|
||||
this.setTypeColumn(splitColumns.includes("Type"));
|
||||
this.setQuickButtonsColumn(splitColumns.includes("QuickButtons"));
|
||||
}
|
||||
};
|
||||
|
||||
@ -108,6 +136,9 @@ class TableStore {
|
||||
case "Modified":
|
||||
this.setModifiedColumn(!this.modifiedColumnIsEnabled);
|
||||
return;
|
||||
case "Erasure":
|
||||
this.setErasureColumn(!this.erasureColumnIsEnabled);
|
||||
return;
|
||||
case "Size":
|
||||
this.setSizeColumn(!this.sizeColumnIsEnabled);
|
||||
return;
|
||||
@ -155,32 +186,50 @@ class TableStore {
|
||||
};
|
||||
|
||||
get tableStorageName() {
|
||||
const { isRoomsFolder, isArchiveFolder } = this.treeFoldersStore;
|
||||
const {
|
||||
isRoomsFolder,
|
||||
isArchiveFolder,
|
||||
isTrashFolder,
|
||||
} = this.treeFoldersStore;
|
||||
const isRooms = isRoomsFolder || isArchiveFolder;
|
||||
const userId = this.authStore.userStore.user.id;
|
||||
|
||||
return isRooms
|
||||
? `${TABLE_ROOMS_COLUMNS}=${userId}`
|
||||
: isTrashFolder
|
||||
? `${TABLE_TRASH_COLUMNS}=${userId}`
|
||||
: `${TABLE_COLUMNS}=${userId}`;
|
||||
}
|
||||
|
||||
get columnStorageName() {
|
||||
const { isRoomsFolder, isArchiveFolder } = this.treeFoldersStore;
|
||||
const {
|
||||
isRoomsFolder,
|
||||
isArchiveFolder,
|
||||
isTrashFolder,
|
||||
} = this.treeFoldersStore;
|
||||
const isRooms = isRoomsFolder || isArchiveFolder;
|
||||
const userId = this.authStore.userStore.user.id;
|
||||
|
||||
return isRooms
|
||||
? `${COLUMNS_ROOMS_SIZE}=${userId}`
|
||||
: isTrashFolder
|
||||
? `${COLUMNS_TRASH_SIZE}=${userId}`
|
||||
: `${COLUMNS_SIZE}=${userId}`;
|
||||
}
|
||||
|
||||
get columnInfoPanelStorageName() {
|
||||
const { isRoomsFolder, isArchiveFolder } = this.treeFoldersStore;
|
||||
const {
|
||||
isRoomsFolder,
|
||||
isArchiveFolder,
|
||||
isTrashFolder,
|
||||
} = this.treeFoldersStore;
|
||||
const isRooms = isRoomsFolder || isArchiveFolder;
|
||||
const userId = this.authStore.userStore.user.id;
|
||||
|
||||
return isRooms
|
||||
? `${COLUMNS_ROOMS_SIZE_INFO_PANEL}=${userId}`
|
||||
: isTrashFolder
|
||||
? `${COLUMNS_TRASH_SIZE_INFO_PANEL}=${userId}`
|
||||
: `${COLUMNS_SIZE_INFO_PANEL}=${userId}`;
|
||||
}
|
||||
|
||||
@ -192,6 +241,11 @@ class TableStore {
|
||||
const userId = this.authStore.userStore.user.id;
|
||||
return `${COLUMNS_ROOMS_SIZE}=${userId}`;
|
||||
}
|
||||
get trashColumnStorageName() {
|
||||
const userId = this.authStore.userStore.user.id;
|
||||
return `${COLUMNS_TRASH_SIZE}=${userId}`;
|
||||
}
|
||||
|
||||
get filesColumnInfoPanelStorageName() {
|
||||
const userId = this.authStore.userStore.user.id;
|
||||
return `${COLUMNS_SIZE_INFO_PANEL}=${userId}`;
|
||||
@ -200,6 +254,10 @@ class TableStore {
|
||||
const userId = this.authStore.userStore.user.id;
|
||||
return `${COLUMNS_ROOMS_SIZE_INFO_PANEL}=${userId}`;
|
||||
}
|
||||
get trashColumnInfoPanelStorageName() {
|
||||
const userId = this.authStore.userStore.user.id;
|
||||
return `${COLUMNS_TRASH_SIZE_INFO_PANEL}=${userId}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default TableStore;
|
||||
|
@ -47,6 +47,8 @@ const FilterInput = React.memo(
|
||||
|
||||
clearSearch,
|
||||
setClearSearch,
|
||||
|
||||
onSortButtonClick,
|
||||
}) => {
|
||||
const [viewSettings, setViewSettings] = React.useState([]);
|
||||
const [inputValue, setInputValue] = React.useState("");
|
||||
@ -161,6 +163,7 @@ const FilterInput = React.memo(
|
||||
viewAs={viewAs === "table" ? "row" : viewAs}
|
||||
viewSettings={viewSettings}
|
||||
onChangeViewAs={onChangeViewAs}
|
||||
onSortButtonClick={onSortButtonClick}
|
||||
viewSelectorVisible={
|
||||
viewSettings &&
|
||||
viewSelectorVisible &&
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
import { withTranslation } from "react-i18next";
|
||||
|
||||
import ComboBox from "@docspace/components/combobox";
|
||||
@ -170,6 +169,8 @@ const SortButton = ({
|
||||
|
||||
onSort,
|
||||
viewSelectorVisible,
|
||||
|
||||
onSortButtonClick,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
|
||||
@ -214,6 +215,10 @@ const SortButton = ({
|
||||
setIsOpen((val) => !val);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
onSortButtonClick && onSortButtonClick(!isOpen);
|
||||
}, [isOpen]);
|
||||
|
||||
const onOptionClick = React.useCallback(
|
||||
(e) => {
|
||||
const key = e.target.closest(".option-item").dataset.value;
|
||||
|
@ -1,722 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
import ImageViewer from "./sub-components/image-viewer";
|
||||
import equal from "fast-deep-equal/react";
|
||||
import Hammer from "hammerjs";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
import { FileStatus } from "@docspace/common/constants";
|
||||
|
||||
import InfoOutlineReactSvgUrl from "PUBLIC_DIR/images/info.outline.react.svg?url";
|
||||
import CopyReactSvgUrl from "PUBLIC_DIR/images/copy.react.svg?url";
|
||||
import DuplicateReactSvgUrl from "PUBLIC_DIR/images/duplicate.react.svg?url";
|
||||
import DownloadReactSvgUrl from "PUBLIC_DIR/images/download.react.svg?url";
|
||||
import DownloadAsReactSvgUrl from "PUBLIC_DIR/images/download-as.react.svg?url";
|
||||
import RenameReactSvgUrl from "PUBLIC_DIR/images/rename.react.svg?url";
|
||||
import TrashReactSvgUrl from "PUBLIC_DIR/images/trash.react.svg?url";
|
||||
import MoveReactSvgUrl from "PUBLIC_DIR/images/duplicate.react.svg?url";
|
||||
|
||||
const mediaTypes = Object.freeze({
|
||||
audio: 1,
|
||||
video: 2,
|
||||
});
|
||||
|
||||
const ButtonKeys = Object.freeze({
|
||||
leftArrow: 37,
|
||||
rightArrow: 39,
|
||||
upArrow: 38,
|
||||
downArrow: 40,
|
||||
space: 32,
|
||||
esc: 27,
|
||||
ctr: 17,
|
||||
one: 49,
|
||||
del: 46,
|
||||
s: 83,
|
||||
});
|
||||
|
||||
let ctrIsPressed = false;
|
||||
class MediaViewer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { playlist, currentFileId, visible } = props;
|
||||
|
||||
const item = playlist.find(
|
||||
(file) => String(file.fileId) === String(currentFileId)
|
||||
);
|
||||
|
||||
if (!item) {
|
||||
console.error("MediaViewer: file not found in playlist", {
|
||||
playlist,
|
||||
currentFileId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const playlistPos = item ? item.id : 0;
|
||||
|
||||
this.state = {
|
||||
visible,
|
||||
allowConvert: true,
|
||||
playlist,
|
||||
playlistPos,
|
||||
fileUrl: item.src,
|
||||
canSwipeImage: true,
|
||||
};
|
||||
|
||||
this.detailsContainer = React.createRef();
|
||||
this.viewerToolbox = React.createRef();
|
||||
}
|
||||
|
||||
updateHammer() {
|
||||
const { playlistPos, playlist } = this.state;
|
||||
|
||||
const currentFile = playlist[playlistPos];
|
||||
const { title } = currentFile;
|
||||
|
||||
const ext = this.getFileExtension(title);
|
||||
const _this = this;
|
||||
|
||||
if (this.hammer) {
|
||||
this.hammer.off("doubletap", this.prevMedia);
|
||||
}
|
||||
this.hammer = null;
|
||||
setTimeout(function () {
|
||||
try {
|
||||
if (_this.canImageView(ext)) {
|
||||
const pinch = new Hammer.Pinch();
|
||||
_this.hammer = Hammer(
|
||||
document.getElementsByClassName("react-viewer-canvas")[0]
|
||||
);
|
||||
_this.hammer.add([pinch]);
|
||||
|
||||
_this.hammer.on("doubletap", _this.doubleTap);
|
||||
}
|
||||
} catch (ex) {
|
||||
//console.error("MediaViewer updateHammer", ex);
|
||||
this.hammer = null;
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const {
|
||||
visible,
|
||||
playlist,
|
||||
currentFileId,
|
||||
onEmptyPlaylistError,
|
||||
} = this.props;
|
||||
|
||||
const { playlistPos, fileUrl } = this.state;
|
||||
const src = playlist[playlistPos]?.src;
|
||||
const title = playlist[playlistPos]?.title;
|
||||
const ext = this.getFileExtension(title);
|
||||
|
||||
if (visible !== prevProps.visible) {
|
||||
const newPlaylistPos =
|
||||
playlist.length > 0
|
||||
? playlist.find((file) => file.fileId === currentFileId).id
|
||||
: 0;
|
||||
|
||||
this.setState({
|
||||
visible: visible,
|
||||
playlistPos: newPlaylistPos,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
src &&
|
||||
src !== fileUrl &&
|
||||
playlistPos === prevState.playlistPos &&
|
||||
ext !== ".tif" &&
|
||||
ext !== ".tiff"
|
||||
) {
|
||||
this.setState({ fileUrl: src });
|
||||
}
|
||||
|
||||
if (
|
||||
visible &&
|
||||
visible === prevProps.visible &&
|
||||
playlistPos !== prevState.playlistPos
|
||||
) {
|
||||
this.updateHammer();
|
||||
if (ext === ".tiff" || ext === ".tif") {
|
||||
this.getTiffDataURL(src);
|
||||
} else {
|
||||
this.setState({ fileUrl: src });
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
visible &&
|
||||
visible === prevProps.visible &&
|
||||
!equal(playlist, prevProps.playlist)
|
||||
) {
|
||||
if (playlist.length > 0) {
|
||||
this.updateHammer();
|
||||
//switching from index to id
|
||||
const newPlaylistPos = currentFileId
|
||||
? playlist.find((file) => file.fileId === currentFileId)?.id ?? 0
|
||||
: 0;
|
||||
|
||||
this.setState({
|
||||
playlist: playlist,
|
||||
playlistPos: newPlaylistPos,
|
||||
});
|
||||
} else {
|
||||
onEmptyPlaylistError();
|
||||
this.setState({
|
||||
visible: false,
|
||||
});
|
||||
}
|
||||
} else if (!equal(playlist, prevProps.playlist)) {
|
||||
this.setState({
|
||||
playlist: playlist,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { playlist, files, setBufferSelection } = this.props;
|
||||
const { playlistPos } = this.state;
|
||||
|
||||
const currentFile = playlist[playlistPos];
|
||||
|
||||
const currentFileId =
|
||||
playlist.length > 0
|
||||
? playlist.find((file) => file.id === playlistPos).fileId
|
||||
: 0;
|
||||
|
||||
const targetFile = files.find((item) => item.id === currentFileId);
|
||||
if (targetFile) setBufferSelection(targetFile);
|
||||
const { src, title } = currentFile;
|
||||
const ext = this.getFileExtension(title);
|
||||
|
||||
if (ext === ".tiff" || ext === ".tif") {
|
||||
this.getTiffDataURL(src);
|
||||
}
|
||||
|
||||
this.updateHammer();
|
||||
|
||||
document.addEventListener("keydown", this.onKeydown, false);
|
||||
document.addEventListener("keyup", this.onKeyup, false);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.hammer) {
|
||||
this.hammer.off("doubletap", this.prevMedia);
|
||||
}
|
||||
document.removeEventListener("keydown", this.onKeydown, false);
|
||||
document.removeEventListener("keyup", this.onKeyup, false);
|
||||
this.onClose();
|
||||
}
|
||||
|
||||
mapSupplied = {
|
||||
".aac": { supply: "m4a", type: mediaTypes.audio },
|
||||
".flac": { supply: "mp3", type: mediaTypes.audio },
|
||||
".m4a": { supply: "m4a", type: mediaTypes.audio },
|
||||
".mp3": { supply: "mp3", type: mediaTypes.audio },
|
||||
".oga": { supply: "oga", type: mediaTypes.audio },
|
||||
".ogg": { supply: "oga", type: mediaTypes.audio },
|
||||
".wav": { supply: "wav", type: mediaTypes.audio },
|
||||
|
||||
".f4v": { supply: "m4v", type: mediaTypes.video },
|
||||
".m4v": { supply: "m4v", type: mediaTypes.video },
|
||||
".mov": { supply: "m4v", type: mediaTypes.video },
|
||||
".mp4": { supply: "m4v", type: mediaTypes.video },
|
||||
".ogv": { supply: "ogv", type: mediaTypes.video },
|
||||
".webm": { supply: "webmv", type: mediaTypes.video },
|
||||
".wmv": { supply: "m4v", type: mediaTypes.video, convertable: true },
|
||||
".avi": { supply: "m4v", type: mediaTypes.video, convertable: true },
|
||||
".mpeg": { supply: "m4v", type: mediaTypes.video, convertable: true },
|
||||
".mpg": { supply: "m4v", type: mediaTypes.video, convertable: true },
|
||||
};
|
||||
|
||||
canImageView = function (ext) {
|
||||
const { extsImagePreviewed } = this.props;
|
||||
return extsImagePreviewed.indexOf(ext) != -1;
|
||||
};
|
||||
|
||||
canPlay = (fileTitle, allowConvert) => {
|
||||
const { extsMediaPreviewed } = this.props;
|
||||
|
||||
const ext =
|
||||
fileTitle[0] === "." ? fileTitle : this.getFileExtension(fileTitle);
|
||||
|
||||
const supply = this.mapSupplied[ext];
|
||||
|
||||
const canConvert = allowConvert || this.props.allowConvert;
|
||||
|
||||
return (
|
||||
!!supply &&
|
||||
extsMediaPreviewed.indexOf(ext) != -1 &&
|
||||
(!supply.convertable || canConvert)
|
||||
);
|
||||
};
|
||||
|
||||
getFileExtension = (fileTitle) => {
|
||||
if (!fileTitle) {
|
||||
return "";
|
||||
}
|
||||
fileTitle = fileTitle.trim();
|
||||
const posExt = fileTitle.lastIndexOf(".");
|
||||
return 0 <= posExt ? fileTitle.substring(posExt).trim().toLowerCase() : "";
|
||||
};
|
||||
|
||||
zoom = 1;
|
||||
|
||||
handleZoomEnd = () => {
|
||||
this.zoom = 1;
|
||||
};
|
||||
|
||||
handleZoomIn = (e) => {
|
||||
if (this.zoom - e.scale > 0.1) {
|
||||
this.zoom = e.scale;
|
||||
document.querySelector('li[data-key="zoomOut"]').click();
|
||||
}
|
||||
};
|
||||
|
||||
handleZoomOut = (e) => {
|
||||
if (e.scale - this.zoom > 0.3) {
|
||||
this.zoom = e.scale;
|
||||
document.querySelector('li[data-key="zoomIn"]').click();
|
||||
}
|
||||
};
|
||||
|
||||
doubleTap = () => {
|
||||
document.querySelector('li[data-key="zoomIn"]')?.click();
|
||||
};
|
||||
|
||||
prevMedia = () => {
|
||||
const { playlistPos, playlist } = this.state;
|
||||
const { setBufferSelection } = this.props;
|
||||
|
||||
let currentPlaylistPos = playlistPos;
|
||||
currentPlaylistPos--;
|
||||
if (currentPlaylistPos === -1) return;
|
||||
if (currentPlaylistPos < 0) currentPlaylistPos = playlist.length - 1;
|
||||
|
||||
const currentFileId = playlist[currentPlaylistPos].fileId;
|
||||
|
||||
const targetFile = this.props.files.find(
|
||||
(item) => item.id === currentFileId
|
||||
);
|
||||
setBufferSelection(targetFile);
|
||||
|
||||
this.setState({
|
||||
playlistPos: currentPlaylistPos,
|
||||
});
|
||||
|
||||
const id = playlist[currentPlaylistPos].fileId;
|
||||
this.props.onChangeUrl(id);
|
||||
};
|
||||
|
||||
nextMedia = () => {
|
||||
const { playlistPos, playlist } = this.state;
|
||||
const { setBufferSelection } = this.props;
|
||||
|
||||
let currentPlaylistPos = playlistPos;
|
||||
currentPlaylistPos = (currentPlaylistPos + 1) % playlist.length;
|
||||
if (currentPlaylistPos === 0) return;
|
||||
|
||||
const currentFileId = playlist[currentPlaylistPos].fileId;
|
||||
|
||||
const targetFile = this.props.files.find(
|
||||
(item) => item.id === currentFileId
|
||||
);
|
||||
setBufferSelection(targetFile);
|
||||
|
||||
this.setState({
|
||||
playlistPos: currentPlaylistPos,
|
||||
});
|
||||
|
||||
const id = playlist[currentPlaylistPos].fileId;
|
||||
this.props.onChangeUrl(id);
|
||||
};
|
||||
|
||||
getOffset = () => {
|
||||
if (this.detailsContainer.current && this.viewerToolbox.current) {
|
||||
return (
|
||||
this.detailsContainer.current.offsetHeight +
|
||||
this.viewerToolbox.current.offsetHeight
|
||||
);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
onDelete = () => {
|
||||
const { playlist, playlistPos } = this.state;
|
||||
|
||||
let currentFileId =
|
||||
playlist.length > 0
|
||||
? playlist.find((file) => file.id === playlistPos).fileId
|
||||
: 0;
|
||||
this.props.onDelete && this.props.onDelete(currentFileId);
|
||||
this.setState({
|
||||
canSwipeImage: false,
|
||||
});
|
||||
};
|
||||
|
||||
onDownload = () => {
|
||||
const { playlist, playlistPos } = this.state;
|
||||
|
||||
let currentFileId =
|
||||
playlist.length > 0
|
||||
? playlist.find((file) => file.id === playlistPos).fileId
|
||||
: 0;
|
||||
this.props.onDownload && this.props.onDownload(currentFileId);
|
||||
};
|
||||
|
||||
onKeyup = (e) => {
|
||||
if (ButtonKeys.ctr === e.keyCode) {
|
||||
ctrIsPressed = false;
|
||||
}
|
||||
};
|
||||
|
||||
onKeydown = (e) => {
|
||||
let isActionKey = false;
|
||||
for (let key in ButtonKeys) {
|
||||
if (ButtonKeys[key] === e.keyCode) {
|
||||
e.preventDefault();
|
||||
isActionKey = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isActionKey) {
|
||||
switch (e.keyCode) {
|
||||
case ButtonKeys.leftArrow:
|
||||
if (document.fullscreenElement) return;
|
||||
this.state.canSwipeImage
|
||||
? ctrIsPressed
|
||||
? document.getElementsByClassName("iconContainer rotateLeft")
|
||||
.length > 0 &&
|
||||
document
|
||||
.getElementsByClassName("iconContainer rotateLeft")[0]
|
||||
.click()
|
||||
: this.prevMedia()
|
||||
: null;
|
||||
break;
|
||||
case ButtonKeys.rightArrow:
|
||||
if (document.fullscreenElement) return;
|
||||
this.state.canSwipeImage
|
||||
? ctrIsPressed
|
||||
? document.getElementsByClassName("iconContainer rotateRight")
|
||||
.length > 0 &&
|
||||
document
|
||||
.getElementsByClassName("iconContainer rotateRight")[0]
|
||||
.click()
|
||||
: this.nextMedia()
|
||||
: null;
|
||||
break;
|
||||
case ButtonKeys.space:
|
||||
document.getElementsByClassName("video-play").length > 0 &&
|
||||
document.getElementsByClassName("video-play")[0].click();
|
||||
break;
|
||||
case ButtonKeys.esc:
|
||||
if (!this.props.deleteDialogVisible) this.props.onClose();
|
||||
break;
|
||||
case ButtonKeys.upArrow:
|
||||
document.getElementsByClassName("iconContainer zoomIn").length > 0 &&
|
||||
document.getElementsByClassName("iconContainer zoomIn")[0].click();
|
||||
break;
|
||||
case ButtonKeys.downArrow:
|
||||
document.getElementsByClassName("iconContainer zoomOut").length > 0 &&
|
||||
document.getElementsByClassName("iconContainer zoomOut")[0].click();
|
||||
break;
|
||||
case ButtonKeys.ctr:
|
||||
ctrIsPressed = true;
|
||||
break;
|
||||
case ButtonKeys.s:
|
||||
if (ctrIsPressed) this.onDownload();
|
||||
break;
|
||||
case ButtonKeys.one:
|
||||
ctrIsPressed &&
|
||||
document.getElementsByClassName("iconContainer reset").length > 0 &&
|
||||
document.getElementsByClassName("iconContainer reset")[0].click();
|
||||
break;
|
||||
case ButtonKeys.del:
|
||||
this.onDelete();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onClose = (e) => {
|
||||
//fix memory leak
|
||||
this.setState({ visible: false });
|
||||
this.props.onClose(e);
|
||||
};
|
||||
|
||||
getTiffDataURL = (src) => {
|
||||
if (!window.Tiff) return;
|
||||
const _this = this;
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.open("GET", src);
|
||||
xhr.onload = function () {
|
||||
try {
|
||||
const tiff = new window.Tiff({ buffer: xhr.response });
|
||||
const dataUrl = tiff.toDataURL();
|
||||
_this.setState({ fileUrl: dataUrl });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { playlistPos, playlist, visible, fileUrl } = this.state;
|
||||
const {
|
||||
t,
|
||||
onClose,
|
||||
userAccess,
|
||||
canDelete,
|
||||
canDownload,
|
||||
errorLabel,
|
||||
isPreviewFile,
|
||||
onClickFavorite,
|
||||
onShowInfoPanel,
|
||||
onClickDownload,
|
||||
onMoveAction,
|
||||
onCopyAction,
|
||||
onDuplicate,
|
||||
onClickDownloadAs,
|
||||
getIcon,
|
||||
onClickRename,
|
||||
onClickDelete,
|
||||
setBufferSelection,
|
||||
files,
|
||||
archiveRoomsId,
|
||||
} = this.props;
|
||||
|
||||
const currentFileId =
|
||||
playlist.length > 0
|
||||
? playlist.find((file) => file.id === playlistPos).fileId
|
||||
: 0;
|
||||
|
||||
const currentFile = playlist[playlistPos];
|
||||
|
||||
const targetFile =
|
||||
files.find((item) => item.id === currentFileId) || playlist[0];
|
||||
|
||||
const archiveRoom =
|
||||
archiveRoomsId === targetFile.rootFolderId ||
|
||||
(!targetFile?.security?.Rename && !targetFile?.security?.Delete);
|
||||
const { title } = currentFile;
|
||||
|
||||
let isImage = false;
|
||||
let isVideo = false;
|
||||
let isAudio = false;
|
||||
let canOpen = true;
|
||||
|
||||
const isFavorite =
|
||||
(playlist[playlistPos].fileStatus & FileStatus.IsFavorite) ===
|
||||
FileStatus.IsFavorite;
|
||||
|
||||
const ext = this.getFileExtension(title);
|
||||
|
||||
const onSetSelectionFile = () => {
|
||||
setBufferSelection(targetFile);
|
||||
};
|
||||
|
||||
const getContextModel = () => {
|
||||
const desktopModel = [
|
||||
{
|
||||
key: "download",
|
||||
label: t("Common:Download"),
|
||||
icon: DownloadReactSvgUrl,
|
||||
onClick: () => onClickDownload(targetFile, t),
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
key: "rename",
|
||||
label: t("Rename"),
|
||||
icon: RenameReactSvgUrl,
|
||||
onClick: () => onClickRename(targetFile),
|
||||
disabled: archiveRoom,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: t("Common:Delete"),
|
||||
icon: TrashReactSvgUrl,
|
||||
onClick: () => onClickDelete(targetFile, t),
|
||||
disabled: archiveRoom,
|
||||
},
|
||||
];
|
||||
|
||||
const model = [
|
||||
{
|
||||
id: "option_room-info",
|
||||
key: "room-info",
|
||||
label: t("Common:Info"),
|
||||
icon: InfoOutlineReactSvgUrl,
|
||||
onClick: () => {
|
||||
return onShowInfoPanel(targetFile);
|
||||
},
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
key: "download",
|
||||
label: t("Common:Download"),
|
||||
icon: DownloadReactSvgUrl,
|
||||
onClick: () => onClickDownload(targetFile, t),
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
key: "move-to",
|
||||
label: t("MoveTo"),
|
||||
icon: MoveReactSvgUrl,
|
||||
onClick: onMoveAction,
|
||||
disabled: !targetFile.security.Move,
|
||||
},
|
||||
// {
|
||||
// key: "download-as",
|
||||
// label: t("Translations:DownloadAs"),
|
||||
// icon: DownloadAsReactSvgUrl, // TODO: uncomment when we can download media by changing the format
|
||||
// onClick: onClickDownloadAs,
|
||||
// disabled: false,
|
||||
// },
|
||||
{
|
||||
id: "option_copy-to",
|
||||
key: "copy-to",
|
||||
label: t("Translations:Copy"),
|
||||
icon: CopyReactSvgUrl,
|
||||
onClick: onCopyAction,
|
||||
disabled: !targetFile.security.Copy,
|
||||
},
|
||||
{
|
||||
id: "option_create-copy",
|
||||
key: "copy",
|
||||
label: t("Common:Duplicate"),
|
||||
icon: DuplicateReactSvgUrl,
|
||||
onClick: () => onDuplicate(targetFile, t),
|
||||
disabled: !targetFile.security.Duplicate,
|
||||
},
|
||||
{
|
||||
key: "rename",
|
||||
label: t("Rename"),
|
||||
icon: RenameReactSvgUrl,
|
||||
onClick: () => onClickRename(targetFile),
|
||||
disabled: !targetFile.security.Rename,
|
||||
},
|
||||
|
||||
{
|
||||
key: "separator0",
|
||||
isSeparator: true,
|
||||
disabled: !targetFile.security.Delete,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: t("Common:Delete"),
|
||||
icon: TrashReactSvgUrl,
|
||||
onClick: () => onClickDelete(targetFile, t),
|
||||
disabled: !targetFile.security.Delete,
|
||||
},
|
||||
];
|
||||
|
||||
return isMobileOnly
|
||||
? model
|
||||
: isImage && !isMobileOnly
|
||||
? desktopModel.filter((el) => el.key !== "download")
|
||||
: desktopModel;
|
||||
};
|
||||
|
||||
if (!this.canPlay(ext) && !this.canImageView(ext)) {
|
||||
canOpen = false;
|
||||
this.props.onError && this.props.onError();
|
||||
}
|
||||
|
||||
if (this.canImageView(ext)) {
|
||||
isImage = true;
|
||||
} else {
|
||||
isImage = false;
|
||||
isVideo = this.mapSupplied[ext]
|
||||
? this.mapSupplied[ext].type == mediaTypes.video
|
||||
: false;
|
||||
isAudio = this.mapSupplied[ext]
|
||||
? this.mapSupplied[ext].type == mediaTypes.audio
|
||||
: false;
|
||||
}
|
||||
|
||||
let audioIcon = getIcon(96, ext);
|
||||
let headerIcon = getIcon(24, ext);
|
||||
|
||||
// TODO: rewrite with fileURL
|
||||
/*if (this.mapSupplied[ext])
|
||||
if (!isImage && this.mapSupplied[ext].convertable && !src.includes("#")) {
|
||||
src += (src.includes("?") ? "&" : "?") + "convpreview=true";
|
||||
}*/
|
||||
return (
|
||||
<>
|
||||
{canOpen && (
|
||||
<ImageViewer
|
||||
userAccess={userAccess}
|
||||
visible={visible}
|
||||
title={title}
|
||||
onClose={this.onClose}
|
||||
images={[{ src: fileUrl, alt: "" }]}
|
||||
inactive={playlist.length <= 1}
|
||||
playlist={playlist}
|
||||
playlistPos={playlistPos}
|
||||
onNextClick={this.nextMedia}
|
||||
onSetSelectionFile={onSetSelectionFile}
|
||||
contextModel={getContextModel}
|
||||
onPrevClick={this.prevMedia}
|
||||
onDeleteClick={this.onDelete}
|
||||
isFavorite={isFavorite}
|
||||
headerIcon={headerIcon}
|
||||
isImage={isImage}
|
||||
isAudio={isAudio}
|
||||
isVideo={isVideo}
|
||||
isPreviewFile={isPreviewFile}
|
||||
audioIcon={audioIcon}
|
||||
onDownloadClick={this.onDownload}
|
||||
archiveRoom={archiveRoom}
|
||||
errorTitle={t("Files:MediaError")}
|
||||
// isFavoritesFolder={isFavoritesFolder}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MediaViewer.propTypes = {
|
||||
allowConvert: PropTypes.bool,
|
||||
visible: PropTypes.bool,
|
||||
currentFileId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
playlist: PropTypes.arrayOf(PropTypes.object),
|
||||
extsImagePreviewed: PropTypes.arrayOf(PropTypes.string),
|
||||
extsMediaPreviewed: PropTypes.arrayOf(PropTypes.string),
|
||||
onError: PropTypes.func,
|
||||
canDelete: PropTypes.func,
|
||||
canDownload: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
onDownload: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
onEmptyPlaylistError: PropTypes.func,
|
||||
deleteDialogVisible: PropTypes.bool,
|
||||
errorLabel: PropTypes.string,
|
||||
isPreviewFile: PropTypes.bool,
|
||||
onChangeUrl: PropTypes.func,
|
||||
};
|
||||
|
||||
MediaViewer.defaultProps = {
|
||||
currentFileId: 0,
|
||||
visible: false,
|
||||
allowConvert: true,
|
||||
canDelete: () => {
|
||||
return true;
|
||||
},
|
||||
canDownload: () => {
|
||||
return true;
|
||||
},
|
||||
isPreviewFile: false,
|
||||
};
|
||||
|
||||
export default MediaViewer;
|
45
packages/common/components/MediaViewer/MediaViewer.props.ts
Normal file
45
packages/common/components/MediaViewer/MediaViewer.props.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { IFile, NumberOrString, PlaylistType, TranslationType } from "./types";
|
||||
export interface MediaViewerProps {
|
||||
t: TranslationType;
|
||||
|
||||
userAccess: boolean;
|
||||
currentFileId: NumberOrString;
|
||||
|
||||
visible: boolean;
|
||||
|
||||
extsMediaPreviewed: string[];
|
||||
extsImagePreviewed: string[];
|
||||
|
||||
deleteDialogVisible: boolean;
|
||||
errorLabel: string;
|
||||
isPreviewFile: boolean;
|
||||
|
||||
files: IFile[];
|
||||
|
||||
playlist: PlaylistType[];
|
||||
|
||||
setBufferSelection: Function;
|
||||
archiveRoomsId: number;
|
||||
|
||||
playlistPos: number;
|
||||
|
||||
getIcon: (size: number, ext: string, ...arg: any) => any;
|
||||
|
||||
onClose: VoidFunction;
|
||||
onError?: VoidFunction;
|
||||
onEmptyPlaylistError: VoidFunction;
|
||||
onDelete: (id: NumberOrString) => void;
|
||||
onDownload: (id: NumberOrString) => void;
|
||||
onChangeUrl: (id: NumberOrString) => void;
|
||||
|
||||
onMoveAction: VoidFunction;
|
||||
onCopyAction: VoidFunction;
|
||||
onClickRename: (file: IFile) => void;
|
||||
onShowInfoPanel: (file: IFile) => void;
|
||||
onDuplicate: (file: IFile, t: TranslationType) => void;
|
||||
onClickDelete: (file: IFile, t: TranslationType) => void;
|
||||
onClickDownload: (file: IFile, t: TranslationType) => void;
|
||||
|
||||
nextMedia: VoidFunction;
|
||||
prevMedia: VoidFunction;
|
||||
}
|
59
packages/common/components/MediaViewer/helpers/index.ts
Normal file
59
packages/common/components/MediaViewer/helpers/index.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { NullOrUndefined, PlaylistType } from "../types";
|
||||
|
||||
export const mediaTypes = Object.freeze({
|
||||
audio: 1,
|
||||
video: 2,
|
||||
});
|
||||
|
||||
export enum KeyboardEventKeys {
|
||||
ArrowRight = "ArrowRight",
|
||||
ArrowLeft = "ArrowLeft",
|
||||
Escape = "Escape",
|
||||
Space = "Space",
|
||||
Delete = "Delete",
|
||||
KeyS = "KeyS",
|
||||
Numpad1 = "Numpad1",
|
||||
Digit1 = "Digit1",
|
||||
}
|
||||
|
||||
export const mapSupplied = {
|
||||
".aac": { supply: "m4a", type: mediaTypes.audio },
|
||||
".flac": { supply: "mp3", type: mediaTypes.audio },
|
||||
".m4a": { supply: "m4a", type: mediaTypes.audio },
|
||||
".mp3": { supply: "mp3", type: mediaTypes.audio },
|
||||
".oga": { supply: "oga", type: mediaTypes.audio },
|
||||
".ogg": { supply: "oga", type: mediaTypes.audio },
|
||||
".wav": { supply: "wav", type: mediaTypes.audio },
|
||||
|
||||
".f4v": { supply: "m4v", type: mediaTypes.video },
|
||||
".m4v": { supply: "m4v", type: mediaTypes.video },
|
||||
".mov": { supply: "m4v", type: mediaTypes.video },
|
||||
".mp4": { supply: "m4v", type: mediaTypes.video },
|
||||
".ogv": { supply: "ogv", type: mediaTypes.video },
|
||||
".webm": { supply: "webmv", type: mediaTypes.video },
|
||||
".wmv": { supply: "m4v", type: mediaTypes.video, convertable: true },
|
||||
".avi": { supply: "m4v", type: mediaTypes.video, convertable: true },
|
||||
".mpeg": { supply: "m4v", type: mediaTypes.video, convertable: true },
|
||||
".mpg": { supply: "m4v", type: mediaTypes.video, convertable: true },
|
||||
} as Record<string, { supply: string; type: number } | undefined>;
|
||||
|
||||
export const isNullOrUndefined = (arg: unknown): arg is NullOrUndefined => {
|
||||
return arg === undefined || arg === null;
|
||||
};
|
||||
|
||||
export const findNearestIndex = (
|
||||
items: PlaylistType[],
|
||||
index: number
|
||||
): number => {
|
||||
if (!Array.isArray(items) || items.length === 0 || index < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let found = items[0].id;
|
||||
for (const item of items) {
|
||||
if (Math.abs(item.id - index) < Math.abs(found - index)) {
|
||||
found = item.id;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
};
|
@ -1 +0,0 @@
|
||||
export default from "./MediaViewer";
|
458
packages/common/components/MediaViewer/index.tsx
Normal file
458
packages/common/components/MediaViewer/index.tsx
Normal file
@ -0,0 +1,458 @@
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
import React, { useState, useCallback, useMemo, useEffect } from "react";
|
||||
|
||||
import ImageViewer from "./sub-components/image-viewer";
|
||||
|
||||
import { MediaViewerProps } from "./MediaViewer.props";
|
||||
import { FileStatus } from "@docspace/common/constants";
|
||||
import {
|
||||
isNullOrUndefined,
|
||||
KeyboardEventKeys,
|
||||
mapSupplied,
|
||||
mediaTypes,
|
||||
} from "./helpers";
|
||||
|
||||
import InfoOutlineReactSvgUrl from "PUBLIC_DIR/images/info.outline.react.svg?url";
|
||||
import CopyReactSvgUrl from "PUBLIC_DIR/images/copy.react.svg?url";
|
||||
import DuplicateReactSvgUrl from "PUBLIC_DIR/images/duplicate.react.svg?url";
|
||||
import DownloadReactSvgUrl from "PUBLIC_DIR/images/download.react.svg?url";
|
||||
import RenameReactSvgUrl from "PUBLIC_DIR/images/rename.react.svg?url";
|
||||
import TrashReactSvgUrl from "PUBLIC_DIR/images/trash.react.svg?url";
|
||||
import MoveReactSvgUrl from "PUBLIC_DIR/images/duplicate.react.svg?url";
|
||||
|
||||
function MediaViewer({
|
||||
playlistPos,
|
||||
nextMedia,
|
||||
prevMedia,
|
||||
...props
|
||||
}: MediaViewerProps): JSX.Element {
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [fileUrl, setFileUrl] = useState<string>(() => {
|
||||
const { playlist, currentFileId } = props;
|
||||
const item = playlist.find(
|
||||
(file) => file.fileId.toString() === currentFileId.toString()
|
||||
);
|
||||
return item?.src ?? "";
|
||||
});
|
||||
|
||||
const [targetFile, setTargetFile] = useState(() => {
|
||||
const { files, currentFileId } = props;
|
||||
return files.find((item) => item.id === currentFileId);
|
||||
});
|
||||
|
||||
const [isFavorite, setIsFavorite] = useState<boolean>(() => {
|
||||
const { playlist } = props;
|
||||
|
||||
return (
|
||||
(playlist[playlistPos].fileStatus & FileStatus.IsFavorite) ===
|
||||
FileStatus.IsFavorite
|
||||
);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const fileId = props.playlist[playlistPos]?.fileId;
|
||||
|
||||
if (!isNullOrUndefined(fileId) && props.currentFileId !== fileId) {
|
||||
props.onChangeUrl(fileId);
|
||||
}
|
||||
}, [props.playlist.length]);
|
||||
|
||||
useEffect(() => {
|
||||
const { playlist, files, setBufferSelection } = props;
|
||||
|
||||
const currentFile = playlist[playlistPos];
|
||||
|
||||
const currentFileId =
|
||||
playlist.length > 0
|
||||
? playlist.find((file) => file.id === playlistPos)?.fileId
|
||||
: 0;
|
||||
|
||||
const targetFile = files.find((item) => item.id === currentFileId);
|
||||
|
||||
if (targetFile) setBufferSelection(targetFile);
|
||||
|
||||
const { src, title } = currentFile;
|
||||
|
||||
const ext = getFileExtension(title);
|
||||
|
||||
if (ext === ".tiff" || ext === ".tif") {
|
||||
fetchAndSetTiffDataURL(src);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const { playlist, onEmptyPlaylistError, files, setBufferSelection } = props;
|
||||
|
||||
const { src, title, fileId } = playlist[playlistPos];
|
||||
const ext = getFileExtension(title);
|
||||
|
||||
if (!src) return onEmptyPlaylistError();
|
||||
|
||||
if (ext !== ".tif" && ext !== ".tiff" && src !== fileUrl) {
|
||||
setFileUrl(src);
|
||||
}
|
||||
|
||||
if (ext === ".tiff" || ext === ".tif") {
|
||||
fetchAndSetTiffDataURL(src);
|
||||
}
|
||||
|
||||
const foundFile = files.find((file) => file.id === fileId);
|
||||
|
||||
if (!isNullOrUndefined(foundFile)) {
|
||||
setTargetFile(foundFile);
|
||||
setBufferSelection(foundFile);
|
||||
}
|
||||
|
||||
setTitle(title);
|
||||
setIsFavorite(
|
||||
(playlist[playlistPos].fileStatus & FileStatus.IsFavorite) ===
|
||||
FileStatus.IsFavorite
|
||||
);
|
||||
}, [
|
||||
props.playlist.length,
|
||||
props.files.length,
|
||||
props.currentFileId,
|
||||
playlistPos,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("keydown", onKeydown);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", onKeydown);
|
||||
};
|
||||
}, [
|
||||
props.playlist.length,
|
||||
props.files.length,
|
||||
playlistPos,
|
||||
props.deleteDialogVisible,
|
||||
]);
|
||||
|
||||
const getContextModel = () => {
|
||||
const {
|
||||
t,
|
||||
onClickDownload,
|
||||
onClickRename,
|
||||
onClickDelete,
|
||||
onShowInfoPanel,
|
||||
onMoveAction,
|
||||
onCopyAction,
|
||||
onDuplicate,
|
||||
} = props;
|
||||
|
||||
if (!targetFile) return [];
|
||||
|
||||
const desktopModel = [
|
||||
{
|
||||
key: "download",
|
||||
label: t("Common:Download"),
|
||||
icon: DownloadReactSvgUrl,
|
||||
onClick: () => onClickDownload(targetFile, t),
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
key: "rename",
|
||||
label: t("Rename"),
|
||||
icon: RenameReactSvgUrl,
|
||||
onClick: () => onClickRename(targetFile),
|
||||
disabled: archiveRoom,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: t("Common:Delete"),
|
||||
icon: TrashReactSvgUrl,
|
||||
onClick: () => onClickDelete(targetFile, t),
|
||||
disabled: archiveRoom,
|
||||
},
|
||||
];
|
||||
|
||||
const model = [
|
||||
{
|
||||
id: "option_room-info",
|
||||
key: "room-info",
|
||||
label: t("Common:Info"),
|
||||
icon: InfoOutlineReactSvgUrl,
|
||||
onClick: () => {
|
||||
return onShowInfoPanel(targetFile);
|
||||
},
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
key: "download",
|
||||
label: t("Common:Download"),
|
||||
icon: DownloadReactSvgUrl,
|
||||
onClick: () => onClickDownload(targetFile, t),
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
key: "move-to",
|
||||
label: t("MoveTo"),
|
||||
icon: MoveReactSvgUrl,
|
||||
onClick: onMoveAction,
|
||||
disabled: !targetFile.security.Move,
|
||||
},
|
||||
{
|
||||
id: "option_copy-to",
|
||||
key: "copy-to",
|
||||
label: t("Translations:Copy"),
|
||||
icon: CopyReactSvgUrl,
|
||||
onClick: onCopyAction,
|
||||
disabled: !targetFile.security.Copy,
|
||||
},
|
||||
{
|
||||
id: "option_create-copy",
|
||||
key: "copy",
|
||||
label: t("Common:Duplicate"),
|
||||
icon: DuplicateReactSvgUrl,
|
||||
onClick: () => onDuplicate(targetFile, t),
|
||||
disabled: !targetFile.security.Duplicate,
|
||||
},
|
||||
{
|
||||
key: "rename",
|
||||
label: t("Rename"),
|
||||
icon: RenameReactSvgUrl,
|
||||
onClick: () => onClickRename(targetFile),
|
||||
disabled: !targetFile.security.Rename,
|
||||
},
|
||||
|
||||
{
|
||||
key: "separator0",
|
||||
isSeparator: true,
|
||||
disabled: !targetFile.security.Delete,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: t("Common:Delete"),
|
||||
icon: TrashReactSvgUrl,
|
||||
onClick: () => onClickDelete(targetFile, t),
|
||||
disabled: !targetFile.security.Delete,
|
||||
},
|
||||
];
|
||||
|
||||
return isMobileOnly
|
||||
? model
|
||||
: isImage && !isMobileOnly
|
||||
? desktopModel.filter((el) => el.key !== "download")
|
||||
: desktopModel;
|
||||
};
|
||||
|
||||
const canImageView = useCallback(
|
||||
(ext: string) => {
|
||||
const { extsImagePreviewed } = props;
|
||||
return extsImagePreviewed.indexOf(ext) != -1;
|
||||
},
|
||||
[props.extsImagePreviewed]
|
||||
);
|
||||
|
||||
const canPlay = useCallback(
|
||||
(fileTitle: string) => {
|
||||
const { extsMediaPreviewed } = props;
|
||||
|
||||
const ext =
|
||||
fileTitle[0] === "." ? fileTitle : getFileExtension(fileTitle);
|
||||
|
||||
const supply = mapSupplied[ext];
|
||||
|
||||
return !!supply && extsMediaPreviewed.indexOf(ext) != -1;
|
||||
},
|
||||
[props.extsMediaPreviewed]
|
||||
);
|
||||
|
||||
const getFileExtension = useCallback((fileTitle: string) => {
|
||||
if (!fileTitle) {
|
||||
return "";
|
||||
}
|
||||
fileTitle = fileTitle.trim();
|
||||
const posExt = fileTitle.lastIndexOf(".");
|
||||
return 0 <= posExt ? fileTitle.substring(posExt).trim().toLowerCase() : "";
|
||||
}, []);
|
||||
|
||||
const onDelete = () => {
|
||||
const { playlist, onDelete } = props;
|
||||
|
||||
let currentFileId = playlist.find((file) => file.id === playlistPos)
|
||||
?.fileId;
|
||||
|
||||
const canDelete = targetFile?.security?.Delete;
|
||||
|
||||
if (!canDelete) return;
|
||||
|
||||
if (!isNullOrUndefined(currentFileId)) onDelete(currentFileId);
|
||||
};
|
||||
|
||||
const onDownload = () => {
|
||||
const { playlist, onDownload } = props;
|
||||
|
||||
let currentFileId = playlist.find((file) => file.id === playlistPos)
|
||||
?.fileId;
|
||||
|
||||
if (!isNullOrUndefined(currentFileId)) onDownload(currentFileId);
|
||||
};
|
||||
|
||||
const onKeydown = (event: KeyboardEvent) => {
|
||||
const { code, ctrlKey } = event;
|
||||
|
||||
if (code in KeyboardEventKeys) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (props.deleteDialogVisible) return;
|
||||
|
||||
switch (code) {
|
||||
case KeyboardEventKeys.ArrowLeft:
|
||||
if (document.fullscreenElement) return;
|
||||
|
||||
if (ctrlKey) {
|
||||
const rotateLeftElement = document.getElementsByClassName(
|
||||
"iconContainer rotateLeft"
|
||||
)?.[0] as HTMLElement | undefined;
|
||||
rotateLeftElement?.click();
|
||||
} else {
|
||||
prevMedia();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case KeyboardEventKeys.ArrowRight:
|
||||
if (document.fullscreenElement) return;
|
||||
|
||||
if (ctrlKey) {
|
||||
const rotateRightElement = document.getElementsByClassName(
|
||||
"iconContainer rotateRight"
|
||||
)?.[0] as HTMLElement | undefined;
|
||||
rotateRightElement?.click();
|
||||
} else {
|
||||
nextMedia();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case KeyboardEventKeys.Space:
|
||||
const videoPlayElement = document.getElementsByClassName(
|
||||
"video-play"
|
||||
)?.[0] as HTMLElement | undefined;
|
||||
|
||||
videoPlayElement?.click();
|
||||
|
||||
break;
|
||||
|
||||
case KeyboardEventKeys.Escape:
|
||||
if (!props.deleteDialogVisible) props.onClose();
|
||||
break;
|
||||
|
||||
case KeyboardEventKeys.KeyS:
|
||||
if (ctrlKey) onDownload();
|
||||
break;
|
||||
|
||||
case KeyboardEventKeys.Digit1:
|
||||
case KeyboardEventKeys.Numpad1:
|
||||
if (ctrlKey) {
|
||||
const resetElement = document.getElementsByClassName(
|
||||
"iconContainer reset"
|
||||
)?.[0] as HTMLElement | undefined;
|
||||
resetElement?.click();
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyboardEventKeys.Delete:
|
||||
onDelete();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
props.onClose();
|
||||
}, [props.onClose]);
|
||||
|
||||
const fetchAndSetTiffDataURL = useCallback((src: string) => {
|
||||
if (!window.Tiff) return;
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.responseType = "arraybuffer";
|
||||
|
||||
xhr.open("GET", src);
|
||||
xhr.onload = function () {
|
||||
try {
|
||||
const tiff = new window.Tiff({ buffer: xhr.response });
|
||||
|
||||
const dataUrl = tiff.toDataURL();
|
||||
|
||||
setFileUrl(dataUrl);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}, []);
|
||||
|
||||
const onSetSelectionFile = useCallback(() => {
|
||||
props.setBufferSelection(targetFile);
|
||||
}, [targetFile]);
|
||||
|
||||
const ext = getFileExtension(title);
|
||||
const images = useMemo(() => [{ src: fileUrl, alt: "" }], [fileUrl]);
|
||||
const audioIcon = useMemo(() => props.getIcon(96, ext), [ext]);
|
||||
const headerIcon = useMemo(() => props.getIcon(24, ext), [ext]);
|
||||
|
||||
let isVideo = false;
|
||||
let isAudio = false;
|
||||
let canOpen = true;
|
||||
let isImage = false;
|
||||
|
||||
const archiveRoom =
|
||||
props.archiveRoomsId === targetFile?.rootFolderId ||
|
||||
(!targetFile?.security?.Rename && !targetFile?.security?.Delete);
|
||||
|
||||
if (canPlay(ext) && canImageView(ext)) {
|
||||
canOpen = false;
|
||||
props.onError?.();
|
||||
}
|
||||
|
||||
if (canImageView(ext)) {
|
||||
isImage = true;
|
||||
} else {
|
||||
isImage = false;
|
||||
isVideo = mapSupplied[ext]
|
||||
? mapSupplied[ext]?.type == mediaTypes.video
|
||||
: false;
|
||||
isAudio = mapSupplied[ext]
|
||||
? mapSupplied[ext]?.type == mediaTypes.audio
|
||||
: false;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{canOpen && (
|
||||
<ImageViewer
|
||||
userAccess={props.userAccess}
|
||||
visible={props.visible}
|
||||
title={title}
|
||||
onClose={onClose}
|
||||
images={images}
|
||||
inactive={props.playlist.length <= 1}
|
||||
playlist={props.playlist}
|
||||
playlistPos={playlistPos}
|
||||
onNextClick={nextMedia}
|
||||
onSetSelectionFile={onSetSelectionFile}
|
||||
contextModel={getContextModel}
|
||||
onPrevClick={prevMedia}
|
||||
onDeleteClick={onDelete}
|
||||
isFavorite={isFavorite}
|
||||
isImage={isImage}
|
||||
isAudio={isAudio}
|
||||
isVideo={isVideo}
|
||||
isPreviewFile={props.isPreviewFile}
|
||||
onDownloadClick={onDownload}
|
||||
archiveRoom={archiveRoom}
|
||||
errorTitle={props.t("Files:MediaError")}
|
||||
headerIcon={headerIcon}
|
||||
audioIcon={audioIcon}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MediaViewer;
|
89
packages/common/components/MediaViewer/types/index.ts
Normal file
89
packages/common/components/MediaViewer/types/index.ts
Normal file
@ -0,0 +1,89 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
Tiff: new (arg: object) => any;
|
||||
}
|
||||
}
|
||||
|
||||
export type TranslationType = (key: string, opt?: object) => string;
|
||||
|
||||
export type NumberOrString = number | string;
|
||||
|
||||
export type NullOrUndefined = null | undefined;
|
||||
|
||||
export type PlaylistType = {
|
||||
id: number;
|
||||
canShare: boolean;
|
||||
fileExst: string;
|
||||
fileId: number;
|
||||
fileStatus: number;
|
||||
src: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
export type CreatedType = {
|
||||
id: string;
|
||||
avatarSmall: string;
|
||||
displayName: string;
|
||||
hasAvatar: boolean;
|
||||
profileUrl: string;
|
||||
};
|
||||
|
||||
export type SecurityType = {
|
||||
Comment: boolean;
|
||||
Copy: boolean;
|
||||
CustomFilter: boolean;
|
||||
Delete: boolean;
|
||||
Duplicate: boolean;
|
||||
Edit: boolean;
|
||||
EditHistory: boolean;
|
||||
FillForms: boolean;
|
||||
Lock: boolean;
|
||||
Move: boolean;
|
||||
Read: boolean;
|
||||
ReadHistory: boolean;
|
||||
Rename: boolean;
|
||||
Review: boolean;
|
||||
};
|
||||
|
||||
export type ViewAccessabilityType = {
|
||||
CoAuhtoring: boolean;
|
||||
Convert: boolean;
|
||||
ImageView: boolean;
|
||||
MediaView: boolean;
|
||||
WebComment: boolean;
|
||||
WebCustomFilterEditing: boolean;
|
||||
WebEdit: boolean;
|
||||
WebRestrictedEditing: boolean;
|
||||
WebReview: boolean;
|
||||
WebView: boolean;
|
||||
};
|
||||
|
||||
export interface IFile {
|
||||
id: number;
|
||||
access: number;
|
||||
canShare: boolean;
|
||||
comment: string;
|
||||
contentLength: string;
|
||||
created: string;
|
||||
createdBy: CreatedType;
|
||||
denyDownload: boolean;
|
||||
denySharing: boolean;
|
||||
fileExst: string;
|
||||
fileStatus: number;
|
||||
fileType: number;
|
||||
folderId: number;
|
||||
pureContentLength: number;
|
||||
rootFolderId: number;
|
||||
rootFolderType: number;
|
||||
security: SecurityType;
|
||||
shared: boolean;
|
||||
thumbnailStatus: number;
|
||||
title: string;
|
||||
updated: string;
|
||||
updatedBy: CreatedType;
|
||||
version: number;
|
||||
versionGroup: number;
|
||||
viewAccessability: ViewAccessabilityType;
|
||||
viewUrl: string;
|
||||
webUrl: string;
|
||||
}
|
@ -76,15 +76,22 @@ const StyledSectionContainer = styled.section`
|
||||
.layout-progress-bar {
|
||||
position: fixed;
|
||||
right: ${(props) =>
|
||||
props.isInfoPanelVisible && !isMobile ? "416px" : "15px"};
|
||||
bottom: 21px;
|
||||
props.isInfoPanelVisible && !isMobile ? "424px" : "24px"};
|
||||
bottom: 24px;
|
||||
}
|
||||
|
||||
.layout-progress-bar_close-icon {
|
||||
position: fixed;
|
||||
right: ${(props) =>
|
||||
props.isInfoPanelVisible && !isMobile ? "480px" : "80px"};
|
||||
bottom: 36px;
|
||||
}
|
||||
|
||||
.layout-progress-second-bar {
|
||||
position: fixed;
|
||||
right: ${(props) =>
|
||||
props.isInfoPanelVisible && !isMobile ? "416px" : "15px"};
|
||||
bottom: 83px;
|
||||
props.isInfoPanelVisible && !isMobile ? "424px" : "24px"};
|
||||
bottom: 96px;
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
|
@ -138,6 +138,7 @@ class SettingsStore {
|
||||
companyInfoSettingsIsDefault = true;
|
||||
|
||||
whiteLabelLogoUrls = [];
|
||||
standalone = false;
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
|
@ -234,6 +234,9 @@ class SelectionArea extends React.Component {
|
||||
passive: false,
|
||||
});
|
||||
document.addEventListener("mouseup", this.onTapStop);
|
||||
|
||||
window.addEventListener("blur", this.onTapStop);
|
||||
|
||||
this.scrollElement.addEventListener("scroll", this.onScroll);
|
||||
};
|
||||
|
||||
@ -242,6 +245,7 @@ class SelectionArea extends React.Component {
|
||||
document.removeEventListener("mousemove", this.onTapMove);
|
||||
|
||||
document.removeEventListener("mouseup", this.onTapStop);
|
||||
window.removeEventListener("blur", this.onTapStop);
|
||||
this.scrollElement.removeEventListener("scroll", this.onScroll);
|
||||
};
|
||||
|
||||
@ -256,7 +260,11 @@ class SelectionArea extends React.Component {
|
||||
folderHeaderHeight,
|
||||
} = this.props;
|
||||
|
||||
if (e.target.closest(".not-selectable")) return;
|
||||
if (
|
||||
e.target.closest(".not-selectable") ||
|
||||
e.target.closest(".row-selected")
|
||||
)
|
||||
return;
|
||||
|
||||
const selectables = document.getElementsByClassName(selectableClass);
|
||||
if (!selectables.length) return;
|
||||
|
@ -455,7 +455,7 @@ const ViewerBase = (props) => {
|
||||
document[funcName]("keydown", handleKeydown, true);
|
||||
}
|
||||
if (viewerCore.current) {
|
||||
viewerCore.current[funcName]("wheel", handleMouseScroll, false);
|
||||
viewerCore.current[funcName]("wheel", handleMouseScroll, {passive: true});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -381,7 +381,7 @@ export default function ViewerPlayer(props) {
|
||||
isFullScreen: false,
|
||||
speedSelection: false,
|
||||
progress: 0,
|
||||
duration: 0,
|
||||
duration: false,
|
||||
speedState: 1,
|
||||
isOpenContext: false,
|
||||
volume: stateVolume,
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "IP ünvan kimi domen dəstəklənmir",
|
||||
"Done": "İcra edildi",
|
||||
"Download": "Endirin",
|
||||
"Duplicate": "Sürətini yaratmaq",
|
||||
"CreateCopy": "Sürətini yaratmaq",
|
||||
"EditButton": "Redaktə edin",
|
||||
"Email": "E-poçt",
|
||||
"EmptyEmail": "Elektron poçt sahəsi boşdur",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Домейни като IP адрес не се поддържат",
|
||||
"Done": "Готово",
|
||||
"Download": "Изтегли",
|
||||
"Duplicate": "Създай копие",
|
||||
"CreateCopy": "Създай копие",
|
||||
"EditButton": "Редактирай",
|
||||
"Email": "Имейл",
|
||||
"EmptyEmail": "Никой имейл не е анализиран",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Domény jako IP adresa nejsou podporovány",
|
||||
"Done": "Hotovo",
|
||||
"Download": "Stáhnout",
|
||||
"Duplicate": "Vytvořit kopii",
|
||||
"CreateCopy": "Vytvořit kopii",
|
||||
"EditButton": "Upravit",
|
||||
"Email": "E-mail",
|
||||
"EmptyEmail": "Nebyl analyzován žádný e-mail",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Domains als IP-Adressen werden nicht unterstützt",
|
||||
"Done": "Fertig",
|
||||
"Download": "Herunterladen",
|
||||
"Duplicate": "Kopie erstellen",
|
||||
"CreateCopy": "Kopie erstellen",
|
||||
"EditButton": "Bearbeiten",
|
||||
"Email": "E-Mail",
|
||||
"EmptyEmail": "Kein E-Mail wurde eingegeben",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Οι τομείς ως διεύθυνση IP δεν υποστηρίζονται",
|
||||
"Done": "Τέλος",
|
||||
"Download": "Λήψη",
|
||||
"Duplicate": "Δημιουργήστε ένα αντίγραφο",
|
||||
"CreateCopy": "Δημιουργήστε ένα αντίγραφο",
|
||||
"EditButton": "Επεξεργασία",
|
||||
"Email": "Email",
|
||||
"EmptyEmail": "Δεν αναλύθηκε email",
|
||||
|
@ -79,7 +79,8 @@
|
||||
"Done": "Done",
|
||||
"DontAskAgain": "Don't ask file name again on creation",
|
||||
"Download": "Download",
|
||||
"Duplicate": "Create copy",
|
||||
"Duplicate": "Duplicate",
|
||||
"CreateCopy": "Create copy",
|
||||
"EditButton": "Edit",
|
||||
"Email": "Email",
|
||||
"EmptyEmail": "No email address parsed",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "No se admiten dominios como dirección IP",
|
||||
"Done": "Listo",
|
||||
"Download": "Descargar",
|
||||
"Duplicate": "Crear una copia",
|
||||
"CreateCopy": "Crear una copia",
|
||||
"EditButton": "Editar",
|
||||
"Email": "Email",
|
||||
"EmptyEmail": "El campo de email está vacío",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Verkkotunnuksia IP-osoitteina ei tueta",
|
||||
"Done": "Tehty",
|
||||
"Download": "Lataa",
|
||||
"Duplicate": "Luo kopio",
|
||||
"CreateCopy": "Luo kopio",
|
||||
"EditButton": "Muokkaa",
|
||||
"Email": "Sähköposti",
|
||||
"EmptyEmail": "Yhtään sähköpostia ei ole jäsennelty",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Les domaines sous forme d'adresse IP ne sont pas pris en charge",
|
||||
"Done": "Terminé",
|
||||
"Download": "Télécharger",
|
||||
"Duplicate": "Créer une copie",
|
||||
"CreateCopy": "Créer une copie",
|
||||
"EditButton": "Modifier",
|
||||
"Email": "E-mail",
|
||||
"EmptyEmail": "Aucun e-mail n'a été analysé",
|
||||
|
@ -65,7 +65,7 @@
|
||||
"DomainIpAddress": "Տիրույթները որպես IP հասցե չեն աջակցվում",
|
||||
"Done": "Ավարտված է",
|
||||
"Download": "Ներբեռնել",
|
||||
"Duplicate": "Ստեղծել պատճեն",
|
||||
"CreateCopy": "Ստեղծել պատճեն",
|
||||
"EditButton": "Խմբագրել",
|
||||
"Email": "Էլ-փոստ",
|
||||
"EmptyEmail": "Էլ․փոստ չի վերլուծվել",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "I domini come indirizzo IP non sono supportati",
|
||||
"Done": "Fine",
|
||||
"Download": "Scarica",
|
||||
"Duplicate": "Crea una copia",
|
||||
"CreateCopy": "Crea una copia",
|
||||
"EditButton": "Modifica",
|
||||
"Email": "Email",
|
||||
"EmptyEmail": "Nessuna email analizzata",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "IPアドレスとしてのドメインには対応していません",
|
||||
"Done": "完了",
|
||||
"Download": "ダウンロード",
|
||||
"Duplicate": "コピーの作成",
|
||||
"CreateCopy": "コピーの作成",
|
||||
"EditButton": "編集",
|
||||
"Email": "メール",
|
||||
"EmptyEmail": "誰もメールを解析しない",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "지원되지 않는 도메인 IP 주소입니다",
|
||||
"Done": "완료",
|
||||
"Download": "다운로드",
|
||||
"Duplicate": "복사본 생성",
|
||||
"CreateCopy": "복사본 생성",
|
||||
"EditButton": "편집",
|
||||
"Email": "이메일",
|
||||
"EmptyEmail": "파싱된 이메일이 하나도 없습니다",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "ທີ່ຢູ່ IP ໂດເມນຍັງບໍ່ຮອງຮັບ",
|
||||
"Done": "ສໍາເລັດ",
|
||||
"Download": "ດາວໂຫຼດ",
|
||||
"Duplicate": "ສ້າງສຳເນົາ",
|
||||
"CreateCopy": "ສ້າງສຳເນົາ",
|
||||
"EditButton": "ແກ້ໄຂ",
|
||||
"Email": "ອີເມວ",
|
||||
"EmptyEmail": "ບໍ່ແບ່ງແຍກອີເມວ",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Domēni kā IP adrese netiek atbalstīti",
|
||||
"Done": "Gatavs",
|
||||
"Download": "Lejupielādēt",
|
||||
"Duplicate": "Izveidojiet kopiju",
|
||||
"CreateCopy": "Izveidojiet kopiju",
|
||||
"EditButton": "Rediģēt",
|
||||
"Email": "E-pasts",
|
||||
"EmptyEmail": "Neviens e-pasts nav parsēts",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Domeinen als IP-adres worden niet ondersteund",
|
||||
"Done": "Gereed",
|
||||
"Download": "Download",
|
||||
"Duplicate": "Maak een kopie",
|
||||
"CreateCopy": "Maak een kopie",
|
||||
"EditButton": "Bewerk",
|
||||
"Email": "E-mail",
|
||||
"EmptyEmail": "Geen enkele e-mail verwerkt",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Domena i adres IP nie są obsługiwane",
|
||||
"Done": "Gotowe",
|
||||
"Download": "Pobierz",
|
||||
"Duplicate": "Utwórz kopię",
|
||||
"CreateCopy": "Utwórz kopię",
|
||||
"EditButton": "Edytuj",
|
||||
"Email": "E-mail",
|
||||
"EmptyEmail": "Adres e-mail nie może być pusty",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Os domínios como endereço IP não são suportados",
|
||||
"Done": "Concluído",
|
||||
"Download": "Baixar",
|
||||
"Duplicate": "Criar uma cópia",
|
||||
"CreateCopy": "Criar uma cópia",
|
||||
"EditButton": "Editar",
|
||||
"Email": "Email",
|
||||
"EmptyEmail": "Ninguém analisou o e-mail",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Os domínios como endereço IP não são suportados",
|
||||
"Done": "Concluído",
|
||||
"Download": "Download",
|
||||
"Duplicate": "Criar uma cópia",
|
||||
"CreateCopy": "Criar uma cópia",
|
||||
"EditButton": "Editar",
|
||||
"Email": "Email",
|
||||
"EmptyEmail": "Sem análise de e-mail",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Domenii sub forma unei adrese IP nu sunt acceptate",
|
||||
"Done": "Gata",
|
||||
"Download": "Descărcare",
|
||||
"Duplicate": "Crează o copie",
|
||||
"CreateCopy": "Crează o copie",
|
||||
"EditButton": "Editare",
|
||||
"Email": "e-mail",
|
||||
"EmptyEmail": "Niciun e-mail nu a fost parsat",
|
||||
|
@ -79,7 +79,8 @@
|
||||
"Done": "Успешно",
|
||||
"DontAskAgain": "Больше не запрашивать имя файла при создании",
|
||||
"Download": "Скачать",
|
||||
"Duplicate": "Создать копию",
|
||||
"Duplicate": "Дублировать",
|
||||
"CreateCopy": "Создать копию",
|
||||
"EditButton": "Редактировать",
|
||||
"Email": "Email",
|
||||
"EmptyEmail": "Поле email пустое",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Domény ako adresa IP nie sú podporované",
|
||||
"Done": "Hotovo",
|
||||
"Download": "Stiahnuť ",
|
||||
"Duplicate": "Vytvoriť kópiu",
|
||||
"CreateCopy": "Vytvoriť kópiu",
|
||||
"EditButton": "Upraviť",
|
||||
"Email": "E-mail",
|
||||
"EmptyEmail": "Žiadny e-mail nebol analyzovaný",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Domene kot IP naslov niso podprte",
|
||||
"Done": "Končano",
|
||||
"Download": "Prenesi",
|
||||
"Duplicate": "Ustvari kopijo",
|
||||
"CreateCopy": "Ustvari kopijo",
|
||||
"EditButton": "Uredi",
|
||||
"Email": "Email",
|
||||
"EmptyEmail": "Noben e-mail ni razčlenjen",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "IP adresi olarak alan adları desteklenmez",
|
||||
"Done": "Tamam",
|
||||
"Download": "İndir",
|
||||
"Duplicate": "Bir kopya oluştur",
|
||||
"CreateCopy": "Bir kopya oluştur",
|
||||
"EditButton": "Düzenle",
|
||||
"Email": "E-posta",
|
||||
"EmptyEmail": "Hiçbir e-posta ayrıştırılmadı",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Домени в якості IP-адрес не підтримуються",
|
||||
"Done": "Готово",
|
||||
"Download": "Завантажити",
|
||||
"Duplicate": "Створити копію",
|
||||
"CreateCopy": "Створити копію",
|
||||
"EditButton": "Редагувати",
|
||||
"Email": "Електронна пошта",
|
||||
"EmptyEmail": "Не проаналізовано жодної адреси електронної пошти",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "Các tên miền dưới dạng địa chỉ IP không được hỗ trợ",
|
||||
"Done": "Đã xong",
|
||||
"Download": "Tải xuống",
|
||||
"Duplicate": "Tạo bản sao",
|
||||
"CreateCopy": "Tạo bản sao",
|
||||
"EditButton": "Chỉnh sửa",
|
||||
"Email": "Email",
|
||||
"EmptyEmail": "Không có email nào được phân tích",
|
||||
|
@ -66,7 +66,7 @@
|
||||
"DomainIpAddress": "不支持使用域作为IP地址",
|
||||
"Done": "完成",
|
||||
"Download": "下载",
|
||||
"Duplicate": "创建副本",
|
||||
"CreateCopy": "创建副本",
|
||||
"EditButton": "编辑",
|
||||
"Email": "电子邮件",
|
||||
"EmptyEmail": "没有解析邮件",
|
||||
|
Loading…
Reference in New Issue
Block a user