Merge branch 'feature/erasure-table-column' into feature/entries-trash-origin

This commit is contained in:
Mushka Nikita 2023-02-13 15:21:28 +03:00
commit 8629cffd4d
73 changed files with 1952 additions and 1297 deletions

View File

@ -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?",

View File

@ -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",

View File

@ -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",

View File

@ -85,6 +85,7 @@
"EnableAutomaticBackup": "Давать возможность автоматически копировать данные",
"EnableAutomaticBackupDescription": "Используйте эту опцию для выполнения резервного копирования данных портала.",
"EnterTitle": "Укажите название",
"EnterPath": "Введите путь",
"EveryDay": "Каждый день",
"EveryMonth": "Каждый месяц",
"EveryWeek": "Каждую неделю",

View File

@ -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}
/>
)}
</>
);
};

View File

@ -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,
};
}
)(

View File

@ -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";

View File

@ -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,
};
}
)(

View File

@ -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,

View File

@ -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,
};
}
)(

View File

@ -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}

View File

@ -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;

View File

@ -11,6 +11,7 @@ import {
StyledBadgesContainer,
StyledQuickButtonsContainer,
} from "../StyledTable";
import ErasureCell from "./ErasureCell";
const RowDataComponent = (props) => {
const {

View File

@ -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));

View File

@ -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,
},
];

View File

@ -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,
};
}
)(

View File

@ -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;

View File

@ -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 {

View File

@ -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}

View File

@ -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}

View File

@ -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 === ""}

View File

@ -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}

View File

@ -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}

View File

@ -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}

View File

@ -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}

View File

@ -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)));

View File

@ -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));

View File

@ -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));

View File

@ -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"])(

View File

@ -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;

View File

@ -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;
}

View File

@ -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,

View File

@ -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;

View File

@ -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 &&

View File

@ -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;

View File

@ -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;

View 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;
}

View 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;
};

View File

@ -1 +0,0 @@
export default from "./MediaViewer";

View 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;

View 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;
}

View File

@ -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) =>

View File

@ -138,6 +138,7 @@ class SettingsStore {
companyInfoSettingsIsDefault = true;
whiteLabelLogoUrls = [];
standalone = false;
constructor() {
makeAutoObservable(this);

View File

@ -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;

View File

@ -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});
}
}

View File

@ -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,

View File

@ -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",

View File

@ -66,7 +66,7 @@
"DomainIpAddress": "Домейни като IP адрес не се поддържат",
"Done": "Готово",
"Download": "Изтегли",
"Duplicate": "Създай копие",
"CreateCopy": "Създай копие",
"EditButton": "Редактирай",
"Email": "Имейл",
"EmptyEmail": "Никой имейл не е анализиран",

View File

@ -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",

View File

@ -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",

View File

@ -66,7 +66,7 @@
"DomainIpAddress": "Οι τομείς ως διεύθυνση IP δεν υποστηρίζονται",
"Done": "Τέλος",
"Download": "Λήψη",
"Duplicate": "Δημιουργήστε ένα αντίγραφο",
"CreateCopy": "Δημιουργήστε ένα αντίγραφο",
"EditButton": "Επεξεργασία",
"Email": "Email",
"EmptyEmail": "Δεν αναλύθηκε email",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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é",

View File

@ -65,7 +65,7 @@
"DomainIpAddress": "Տիրույթները որպես IP հասցե չեն աջակցվում",
"Done": "Ավարտված է",
"Download": "Ներբեռնել",
"Duplicate": "Ստեղծել պատճեն",
"CreateCopy": "Ստեղծել պատճեն",
"EditButton": "Խմբագրել",
"Email": "Էլ-փոստ",
"EmptyEmail": "Էլ․փոստ չի վերլուծվել",

View File

@ -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",

View File

@ -66,7 +66,7 @@
"DomainIpAddress": "IPアドレスとしてのドメインには対応していません",
"Done": "完了",
"Download": "ダウンロード",
"Duplicate": "コピーの作成",
"CreateCopy": "コピーの作成",
"EditButton": "編集",
"Email": "メール",
"EmptyEmail": "誰もメールを解析しない",

View File

@ -66,7 +66,7 @@
"DomainIpAddress": "지원되지 않는 도메인 IP 주소입니다",
"Done": "완료",
"Download": "다운로드",
"Duplicate": "복사본 생성",
"CreateCopy": "복사본 생성",
"EditButton": "편집",
"Email": "이메일",
"EmptyEmail": "파싱된 이메일이 하나도 없습니다",

View File

@ -66,7 +66,7 @@
"DomainIpAddress": "ທີ່ຢູ່ IP ໂດເມນຍັງບໍ່ຮອງຮັບ",
"Done": "ສໍາເລັດ",
"Download": "ດາວໂຫຼດ",
"Duplicate": "ສ້າງສຳເນົາ",
"CreateCopy": "ສ້າງສຳເນົາ",
"EditButton": "ແກ້ໄຂ",
"Email": "ອີເມວ",
"EmptyEmail": "ບໍ່ແບ່ງແຍກອີເມວ",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -79,7 +79,8 @@
"Done": "Успешно",
"DontAskAgain": "Больше не запрашивать имя файла при создании",
"Download": "Скачать",
"Duplicate": "Создать копию",
"Duplicate": "Дублировать",
"CreateCopy": "Создать копию",
"EditButton": "Редактировать",
"Email": "Email",
"EmptyEmail": "Поле email пустое",

View File

@ -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ý",

View File

@ -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",

View File

@ -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ı",

View File

@ -66,7 +66,7 @@
"DomainIpAddress": "Домени в якості IP-адрес не підтримуються",
"Done": "Готово",
"Download": "Завантажити",
"Duplicate": "Створити копію",
"CreateCopy": "Створити копію",
"EditButton": "Редагувати",
"Email": "Електронна пошта",
"EmptyEmail": "Не проаналізовано жодної адреси електронної пошти",

View File

@ -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",

View File

@ -66,7 +66,7 @@
"DomainIpAddress": "不支持使用域作为IP地址",
"Done": "完成",
"Download": "下载",
"Duplicate": "创建副本",
"CreateCopy": "创建副本",
"EditButton": "编辑",
"Email": "电子邮件",
"EmptyEmail": "没有解析邮件",