Merge branch 'bugfix/61157' of github.com:ONLYOFFICE/DocSpace into bugfix/61157

This commit is contained in:
Elyor Djalilov 2023-02-16 14:31:42 +05:00
commit 0dd019201b
77 changed files with 945 additions and 625 deletions

View File

@ -244,17 +244,17 @@ public class EmployeeFullDtoHelper : EmployeeDtoHelper
if (_context.Check("avatarMax"))
{
result.AvatarMax = await _userPhotoManager.GetMaxPhotoURL(userInfo.Id) + $"?_={cacheKey}";
result.AvatarMax = await _userPhotoManager.GetMaxPhotoURL(userInfo.Id) + $"?hash={cacheKey}";
}
if (_context.Check("avatarMedium"))
{
result.AvatarMedium = await _userPhotoManager.GetMediumPhotoURL(userInfo.Id) + $"?_={cacheKey}";
result.AvatarMedium = await _userPhotoManager.GetMediumPhotoURL(userInfo.Id) + $"?hash={cacheKey}";
}
if (_context.Check("avatar"))
{
result.Avatar = await _userPhotoManager.GetBigPhotoURL(userInfo.Id) + $"?_={cacheKey}";
result.Avatar = await _userPhotoManager.GetBigPhotoURL(userInfo.Id) + $"?hash={cacheKey}";
}
if (_context.Check("listAdminModules"))

View File

@ -73,9 +73,9 @@
"DeleteTheme": "Delete theme",
"DeleteThemeForever": "Delete theme forever?",
"DeleteThemeNotice": "The theme will be deleted permanently. You will not be able to undo this action.",
"DeveloperTools": "Developer",
"DeveloperTools": "Developer Tools",
"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

@ -73,7 +73,7 @@
"DeleteTheme": "Удалить тему",
"DeleteThemeForever": "Удалить тему навсегда?",
"DeleteThemeNotice": "Тема будет удалена навсегда. Вы не сможете отменить это действие.",
"DeveloperTools": "Разработчик",
"DeveloperTools": "Инструменты разработчика",
"Disabled": "Отключено",
"DownloadCopy": "Скачать копию",
"DownloadReportBtn": "Скачать и открыть отчет",
@ -85,6 +85,7 @@
"EnableAutomaticBackup": "Давать возможность автоматически копировать данные",
"EnableAutomaticBackupDescription": "Используйте эту опцию для выполнения резервного копирования данных портала.",
"EnterTitle": "Укажите название",
"EnterPath": "Введите путь",
"EveryDay": "Каждый день",
"EveryMonth": "Каждый месяц",
"EveryWeek": "Каждую неделю",

View File

@ -87,7 +87,9 @@ export default function withFileActions(WrappedFileItem) {
const { isThirdPartyFolder } = item;
const notSelectable = e.target.closest(".not-selectable");
const isFileName = e.target.classList.contains("item-file-name");
const isFileName =
e.target.classList.contains("item-file-name") ||
e.target.classList.contains("row-content-link");
if (
isPrivacy ||
@ -225,7 +227,7 @@ export default function withFileActions(WrappedFileItem) {
itemIndex,
} = this.props;
const { fileExst, access, id } = item;
const { access, id } = item;
const isDragging = isFolder && access < 2 && !isTrashFolder && !isPrivacy;
@ -233,11 +235,7 @@ export default function withFileActions(WrappedFileItem) {
if (draggable) className += " draggable";
let value = !item.isFolder ? `file_${id}` : `folder_${id}`;
value += draggable
? "_draggable"
: item.providerKey
? `_${item.providerKey}`
: "_false";
value += draggable ? "_draggable" : "_false";
value += `_index_${itemIndex}`;

View File

@ -298,7 +298,7 @@ const Items = ({
const onMoveTo = React.useCallback(
(destFolderId, title) => {
moveDragItems(destFolderId, title, null, {
moveDragItems(destFolderId, title, {
copy: t("Translations:CopyOperation"),
move: t("Translations:MoveToOperation"),
});

View File

@ -212,10 +212,6 @@ const paddingCss = css`
@media ${desktop} {
padding-right: 3px;
}
@media ${tablet} {
margin-left: -1px;
}
`;
const StyledGridWrapper = styled.div`

View File

@ -15,7 +15,7 @@ const StyledInfoPanelBody = styled.div`
: css`
padding: 80px 3px 0 20px;
@media ${hugeMobile} {
padding: 80px 8px 0 16px;
padding: 80px 0 0 16px;
}
`}

View File

@ -1,5 +1,5 @@
import styled from "styled-components";
import { isMobileOnly } from "react-device-detect";
import { Base } from "@docspace/components/themes";
const StyledThumbnail = styled.div`
@ -7,13 +7,14 @@ const StyledThumbnail = styled.div`
justify-content: center;
align-items: center;
width: 100%;
height: auto;
height: ${isMobileOnly ? "188" : "240"}px;
img {
border: ${(props) => `solid 1px ${props.theme.infoPanel.borderColor}`};
border-radius: 6px;
width: auto;
max-width: 100%;
height: auto;
width: 100%;
height: 100%;
object-fit: none;
object-position: top;
}
`;
@ -30,7 +31,8 @@ const StyledNoThumbnail = styled.div`
border-radius: 16px;
}
.custom-logo {
outline: 1px solid ${(props) => props.theme.infoPanel.details.customLogoBorderColor};
outline: 1px solid ${(props) =>
props.theme.infoPanel.details.customLogoBorderColor};
`;
const StyledAccess = styled.div`

View File

@ -1,11 +1,11 @@
import styled from "styled-components";
import { isMobileOnly } from "react-device-detect";
import { Base } from "@docspace/components/themes";
const StyledGalleryThumbnail = styled.div`
box-sizing: border-box;
width: 100%;
height: 346px;
height: ${isMobileOnly ? "335" : "346"}px;
overflow: hidden;
border: ${(props) =>
`solid 1px ${props.theme.infoPanel.gallery.borderColor}`};

View File

@ -27,6 +27,7 @@ const FilesMediaViewer = (props) => {
setToPreviewFile,
setScrollToItem,
setCurrentId,
setAlreadyFetchingRooms,
setBufferSelection,
isFavoritesFolder,
archiveRoomsId,
@ -72,6 +73,7 @@ const FilesMediaViewer = (props) => {
fetchFiles(previewFile.folderId).finally(() => {
setIsLoading(false);
setFirstLoad(false);
setAlreadyFetchingRooms(false);
});
}
}, [previewFile]);
@ -230,6 +232,7 @@ export default inject(
isPreview,
resetUrl,
setSelection,
setAlreadyFetchingRooms,
} = filesStore;
const {
visible,
@ -286,6 +289,7 @@ export default inject(
setScrollToItem,
setCurrentId,
setBufferSelection,
setAlreadyFetchingRooms,
isFavoritesFolder,
onClickFavorite,
onClickDownloadAs,

View File

@ -95,12 +95,14 @@ const FileTile = (props) => {
/>
);
const activeClass = checkedProps || isActive ? "tile-selected" : "";
return (
<div ref={props.selectableRef} id={id}>
<StyledDragAndDrop
data-title={item.title}
value={value}
className={`files-item ${className} ${item.id}_${item.fileExst}`}
className={`files-item ${className} ${activeClass} ${item.id}_${item.fileExst}`}
onDrop={onDrop}
onMouseDown={onMouseDown}
dragging={dragging && isDragging}

View File

@ -261,7 +261,7 @@ const StyledFileTileTop = styled.div`
position: absolute;
height: 100%;
width: 100%;
object-fit: ${(props) => (props.isMedia ? "cover" : "none")};
object-fit: none;
object-position: top;
z-index: 0;
border-radius: 6px 6px 0 0;

View File

@ -188,7 +188,6 @@ const SectionBodyContent = (props) => {
const splitValue = treeDataValue && treeDataValue.split(" ");
const isDragging = splitValue && splitValue.includes("dragging");
const treeValue = isDragging ? splitValue[0] : null;
const treeProvider = splitValue && splitValue[splitValue.length - 1];
const elem = e.target.closest(".droppable");
const title = elem && elem.dataset.title;
@ -201,21 +200,20 @@ const SectionBodyContent = (props) => {
}
const folderId = value ? value.split("_")[1] : treeValue;
const providerKey = value ? value.split("_")[2]?.trim() : treeProvider;
setStartDrag(false);
setDragging(false);
onMoveTo(folderId, title, providerKey);
onMoveTo(folderId, title);
isDragActive = false;
return;
};
const onMoveTo = (destFolderId, title, providerKey) => {
const onMoveTo = (destFolderId, title) => {
const id = isNaN(+destFolderId) ? destFolderId : +destFolderId;
moveDragItems(id, title, providerKey, {
moveDragItems(id, title, {
copy: t("Common:CopyOperation"),
move: t("Translations:MoveToOperation"),
}); //TODO: then catch
});
};
const onDropEvent = () => {

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

@ -55,6 +55,7 @@ let timeout = null,
CancelToken,
source;
const backUrl = window.location.origin;
const PriceCalculation = ({
t,
user,
@ -71,13 +72,19 @@ const PriceCalculation = ({
currencySymbol,
isAlreadyPaid,
isFreeAfterPaidPeriod,
setStartPaymentLink,
managersCount,
}) => {
useEffect(() => {
useEffect(async () => {
initializeInfo();
!isAlreadyPaid && setStartPaymentLink(t);
if (isAlreadyPaid) return;
try {
const link = await getPaymentLink(managersCount, source?.token, backUrl);
setPaymentLink(link);
} catch (e) {
toastr.error(t("ErrorNotification"));
}
return () => {
timeout && clearTimeout(timeout);
@ -103,7 +110,7 @@ const PriceCalculation = ({
CancelToken = axios.CancelToken;
source = CancelToken.source();
await getPaymentLink(value, source.token)
await getPaymentLink(value, source.token, backUrl)
.then((link) => {
setPaymentLink(link);
setIsLoading(false);
@ -207,7 +214,6 @@ export default inject(({ auth, payments }) => {
setManagersCount,
maxAvailableManagersCount,
initializeInfo,
setStartPaymentLink,
managersCount,
} = payments;
const { theme } = auth.settingsStore;
@ -224,7 +230,7 @@ export default inject(({ auth, payments }) => {
return {
managersCount,
setStartPaymentLink,
isFreeTariff,
setManagersCount,
tariffsInfo,

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

@ -1266,24 +1266,15 @@ class FilesActionStore {
.finally(() => setTimeout(() => clearSecondaryProgressData(), TIMEOUT));
};
moveDragItems = (destFolderId, folderTitle, providerKey, translations) => {
moveDragItems = (destFolderId, folderTitle, translations) => {
const folderIds = [];
const fileIds = [];
const deleteAfter = false;
const { selection } = this.filesStore;
const { isRootFolder } = this.selectedFolderStore;
const {
isShareFolder,
isCommonFolder,
isFavoritesFolder,
isRecentFolder,
} = this.treeFoldersStore;
const isCopy =
isShareFolder ||
isFavoritesFolder ||
isRecentFolder ||
(!this.authStore.isAdmin && isCommonFolder);
const isCopy = selection.findIndex((f) => f.security.Move) === -1;
const operationData = {
destFolderId,

View File

@ -2438,7 +2438,7 @@ class FilesStore {
const previewUrl = canOpenPlayer
? this.getItemUrl(id, false, needConvert, canOpenPlayer)
: null;
const contextOptions = this.getFilesContextOptions(item, canOpenPlayer);
const contextOptions = this.getFilesContextOptions(item);
const isThirdPartyFolder = providerKey && id === rootFolderId;
const iconSize = this.viewAs === "table" ? 24 : 32;

View File

@ -140,15 +140,6 @@ class PaymentStore {
}
};
setStartPaymentLink = async (t) => {
try {
const link = await api.portal.getPaymentLink(this.managersCount);
this.setPaymentLink(link);
} catch (e) {
toastr.error(t("ErrorNotification"));
}
};
setTotalPrice = (value) => {
const price = this.getTotalCostByFormula(value);
if (price !== this.totalPrice) this.totalPrice = price;

View File

@ -239,12 +239,13 @@ export function getPaymentAccount() {
return request({ method: "get", url: "/portal/payment/account" });
}
export function getPaymentLink(adminCount, cancelToken) {
export function getPaymentLink(adminCount, cancelToken, backUrl) {
return request({
method: "put",
url: `/portal/payment/url`,
data: {
quantity: { admin: adminCount },
backUrl,
},
cancelToken,
});

View File

@ -23,7 +23,7 @@ export interface MediaViewerProps {
playlistPos: number;
getIcon: (size: number, ext: string, ...arg: any) => any;
getIcon: (size: number, ext: string, ...arg: any) => string;
onClose: VoidFunction;
onError?: VoidFunction;

View File

@ -0,0 +1,112 @@
import React from "react";
import MediaZoomInIcon from "PUBLIC_DIR/images/media.zoomin.react.svg";
import MediaZoomOutIcon from "PUBLIC_DIR/images/media.zoomout.react.svg";
import MediaRotateLeftIcon from "PUBLIC_DIR/images/media.rotateleft.react.svg";
import MediaRotateRightIcon from "PUBLIC_DIR/images/media.rotateright.react.svg";
import MediaDeleteIcon from "PUBLIC_DIR/images/media.delete.react.svg";
import MediaDownloadIcon from "PUBLIC_DIR/images/download.react.svg";
import MediaFavoriteIcon from "PUBLIC_DIR/images/favorite.react.svg";
import ViewerSeparator from "PUBLIC_DIR/images/viewer.separator.react.svg";
export const getCustomToolbar = (
onDeleteClick: VoidFunction,
onDownloadClick: VoidFunction
) => {
return [
{
key: "zoomOut",
percent: true,
actionType: 2,
render: (
<div className="iconContainer zoomOut">
<MediaZoomOutIcon size="scale" />
</div>
),
},
{
key: "percent",
actionType: 999,
},
{
key: "zoomIn",
actionType: 1,
render: (
<div className="iconContainer zoomIn">
<MediaZoomInIcon size="scale" />
</div>
),
},
{
key: "rotateLeft",
actionType: 5,
render: (
<div className="iconContainer rotateLeft">
<MediaRotateLeftIcon size="scale" />
</div>
),
},
{
key: "rotateRight",
actionType: 6,
render: (
<div className="iconContainer rotateRight">
<MediaRotateRightIcon size="scale" />
</div>
),
},
{
key: "separator download-separator",
actionType: -1,
noHover: true,
render: (
<div className="separator" style={{ height: "16px" }}>
<ViewerSeparator size="scale" />
</div>
),
},
{
key: "download",
actionType: 102,
render: (
<div className="iconContainer download" style={{ height: "16px" }}>
<MediaDownloadIcon size="scale" />
</div>
),
onClick: onDownloadClick,
},
{
key: "context-separator",
actionType: -1,
noHover: true,
render: (
<div className="separator" style={{ height: "16px" }}>
<ViewerSeparator size="scale" />
</div>
),
},
{
key: "context-menu",
actionType: -1,
},
{
key: "delete",
actionType: 103,
render: (
<div className="iconContainer viewer-delete">
<MediaDeleteIcon size="scale" />
</div>
),
onClick: onDeleteClick,
},
{
key: "favorite",
actionType: 104,
render: (
<div className="iconContainer viewer-favorite">
<MediaFavoriteIcon size="scale" />
</div>
),
},
];
};

View File

@ -1,4 +1,9 @@
import { NullOrUndefined, PlaylistType } from "../types";
import {
ContextMenuModel,
NullOrUndefined,
PlaylistType,
SeparatorType,
} from "../types";
export const mediaTypes = Object.freeze({
audio: 1,
@ -57,3 +62,7 @@ export const findNearestIndex = (
}
return found;
};
export const isSeparator = (arg: ContextMenuModel): arg is SeparatorType => {
return arg?.isSeparator;
};

View File

@ -1,7 +1,7 @@
import { isMobileOnly } from "react-device-detect";
import React, { useState, useCallback, useMemo, useEffect } from "react";
import ImageViewer from "./sub-components/image-viewer";
import ViewerWrapper from "./sub-components/ViewerWrapper";
import { MediaViewerProps } from "./MediaViewer.props";
import { FileStatus } from "@docspace/common/constants";
@ -27,7 +27,6 @@ function MediaViewer({
...props
}: MediaViewerProps): JSX.Element {
const [title, setTitle] = useState<string>("");
const [canSwipeImage, setCanSwipeImage] = useState<boolean>(true);
const [fileUrl, setFileUrl] = useState<string>(() => {
const { playlist, currentFileId } = props;
const item = playlist.find(
@ -79,12 +78,6 @@ function MediaViewer({
if (ext === ".tiff" || ext === ".tif") {
fetchAndSetTiffDataURL(src);
}
document.addEventListener("keydown", onKeydown);
return () => {
document.removeEventListener("keydown", onKeydown);
};
}, []);
useEffect(() => {
@ -122,6 +115,19 @@ function MediaViewer({
playlistPos,
]);
useEffect(() => {
document.addEventListener("keydown", onKeydown);
return () => {
document.removeEventListener("keydown", onKeydown);
};
}, [
props.playlist.length,
props.files.length,
playlistPos,
props.deleteDialogVisible,
]);
const getContextModel = () => {
const {
t,
@ -267,7 +273,10 @@ function MediaViewer({
let currentFileId = playlist.find((file) => file.id === playlistPos)
?.fileId;
setCanSwipeImage(false);
const canDelete = targetFile?.security?.Delete;
if (!canDelete) return;
if (!isNullOrUndefined(currentFileId)) onDelete(currentFileId);
};
@ -286,10 +295,11 @@ function MediaViewer({
if (code in KeyboardEventKeys) {
event.preventDefault();
}
if (props.deleteDialogVisible) return;
switch (code) {
case KeyboardEventKeys.ArrowLeft:
if (document.fullscreenElement || !canSwipeImage) return;
if (document.fullscreenElement) return;
if (ctrlKey) {
const rotateLeftElement = document.getElementsByClassName(
@ -303,7 +313,7 @@ function MediaViewer({
break;
case KeyboardEventKeys.ArrowRight:
if (document.fullscreenElement || !canSwipeImage) return;
if (document.fullscreenElement) return;
if (ctrlKey) {
const rotateRightElement = document.getElementsByClassName(
@ -415,7 +425,7 @@ function MediaViewer({
return (
<>
{canOpen && (
<ImageViewer
<ViewerWrapper
userAccess={props.userAccess}
visible={props.visible}
title={title}

View File

@ -0,0 +1,6 @@
import styled from "styled-components";
import DropDown from "@docspace/components/drop-down";
export const StyledDropDown = styled(DropDown)`
background: #333;
`;

View File

@ -0,0 +1,16 @@
import styled from "styled-components";
import DropDownItem from "@docspace/components/drop-down-item";
export const StyledDropDownItem = styled(DropDownItem)`
color: #fff;
.drop-down-item_icon svg {
path {
fill: #fff !important;
}
}
&:hover {
background: #444;
}
`;

View File

@ -0,0 +1,33 @@
import { ContextMenuModel, PlaylistType } from "../../types";
interface ViewerWrapperProps {
userAccess: boolean;
visible: boolean;
title: string;
images: { src: string; alt: string }[];
inactive: boolean;
playlist: PlaylistType[];
playlistPos: number;
isFavorite: boolean;
isImage: boolean;
isAudio: boolean;
isVideo: boolean;
isPreviewFile: boolean;
archiveRoom: boolean;
errorTitle: string;
headerIcon: string;
audioIcon: string;
onClose: VoidFunction;
onPrevClick: VoidFunction;
onNextClick: VoidFunction;
onDeleteClick: VoidFunction;
onDownloadClick: VoidFunction;
onSetSelectionFile: VoidFunction;
contextModel: () => ContextMenuModel[];
}
export default ViewerWrapperProps;

View File

@ -0,0 +1,120 @@
import React, { useMemo, memo, useCallback } from "react";
import equal from "fast-deep-equal/react";
import { Viewer } from "@docspace/components/viewer";
import { isSeparator } from "../../helpers";
import { getCustomToolbar } from "../../helpers/getCustomToolbar";
import { ContextMenuModel } from "../../types";
import { StyledDropDown } from "../StyledDropDown";
import { StyledDropDownItem } from "../StyledDropDownItem";
import ViewerWrapperProps from "./ViewerWrapper.props";
const DefaultSpeedZoom = 0.25;
function ViewerWrapper(props: ViewerWrapperProps) {
const onClickContextItem = useCallback(
(item: ContextMenuModel) => {
if (isSeparator(item)) return;
item.onClick();
props.onClose();
},
[props.onClose]
);
const generateContextMenu = (
isOpen: boolean,
right: string,
bottom: string
) => {
const model = props.contextModel();
return (
<StyledDropDown
open={isOpen}
isDefaultMode={false}
directionY="top"
directionX="right"
fixedDirection={true}
withBackdrop={false}
manualY={(bottom || "63") + "px"}
manualX={(right || "-31") + "px"}
>
{model.map((item) => {
if (item.disabled) return;
const isItemSeparator = isSeparator(item);
return (
<StyledDropDownItem
className={`${item.isSeparator ? "is-separator" : ""}`}
key={item.key}
label={isItemSeparator ? undefined : item.label}
icon={!isItemSeparator && item.icon ? item.icon : ""}
onClick={() => onClickContextItem(item)}
/>
);
})}
</StyledDropDown>
);
};
const toolbars = useMemo(() => {
const {
onDeleteClick,
onDownloadClick,
playlist,
playlistPos,
userAccess,
} = props;
const customToolbar = getCustomToolbar(onDeleteClick, onDownloadClick);
const canShare = playlist[playlistPos].canShare;
const toolbars =
!canShare && userAccess
? customToolbar.filter(
(x) => x.key !== "share" && x.key !== "share-separator"
)
: customToolbar.filter((x) => x.key !== "delete");
return toolbars;
}, [
props.onDeleteClick,
props.onDownloadClick,
props.playlist,
props.playlistPos,
props.userAccess,
]);
return (
<Viewer
title={props.title}
images={props.images}
isAudio={props.isAudio}
isVideo={props.isVideo}
visible={props.visible}
isImage={props.isImage}
playlist={props.playlist}
inactive={props.inactive}
audioIcon={props.audioIcon}
zoomSpeed={DefaultSpeedZoom}
errorTitle={props.errorTitle}
headerIcon={props.headerIcon}
customToolbar={() => toolbars}
playlistPos={props.playlistPos}
archiveRoom={props.archiveRoom}
isPreviewFile={props.isPreviewFile}
onMaskClick={props.onClose}
onNextClick={props.onNextClick}
onPrevClick={props.onPrevClick}
contextModel={props.contextModel}
onDownloadClick={props.onDownloadClick}
generateContextMenu={generateContextMenu}
onSetSelectionFile={props.onSetSelectionFile}
/>
);
}
export default memo(ViewerWrapper, (prevProps, nextProps) =>
equal(prevProps, nextProps)
);

View File

@ -87,3 +87,21 @@ export interface IFile {
viewUrl: string;
webUrl: string;
}
export type ContextMenuType = {
id?: string;
key: string;
label: string;
icon: string;
disabled: boolean;
onClick: VoidFunction;
isSeparator?: undefined;
};
export type SeparatorType = {
key: string;
isSeparator: boolean;
disabled: boolean;
};
export type ContextMenuModel = ContextMenuType | SeparatorType;

View File

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

View File

@ -262,10 +262,19 @@ class SelectionArea extends React.Component {
if (
e.target.closest(".not-selectable") ||
e.target.closest(".row-selected")
e.target.closest(".tile-selected") ||
e.target.closest(".table-row-selected") ||
e.target.closest(".row-selected") ||
!e.target.closest("#sectionScroll")
)
return;
if (e.target.tagName === "A") {
const node = e.target.closest("." + selectableClass);
onMove && onMove({ added: [node], removed: [], clear: true });
return;
}
const selectables = document.getElementsByClassName(selectableClass);
if (!selectables.length) return;

View File

@ -5,6 +5,7 @@ const StyledSelectionArea = styled.div`
left: 0;
position: fixed;
margin: 0;
display: none;
background: rgba(68, 170, 255, 0.5);
border: 1px solid #4af;

View File

@ -80,12 +80,12 @@ export const Viewer = (props) => {
};
}
return () => document.removeEventListener("touchstart", onTouch);
}, [isPlay, isOpenContextMenu]);
}, [isPlay, isOpenContextMenu, isImage]);
function resetTimer() {
setPanelVisible(true);
clearTimeout(timer);
timer = setTimeout(() => setPanelVisible(false), 5000);
timer = setTimeout(() => setPanelVisible(false), 2500);
setImageTimer(timer);
}

View File

@ -240,6 +240,10 @@ const StyledMobileDetails = styled.div`
.title {
font-weight: 600;
margin-top: 6px;
width: calc(100% - 100px);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
`;

View File

@ -907,7 +907,7 @@ export default function ViewerPlayer(props) {
}
if (isMobileOnly && videoRef.current && displayUI) {
clearTimeout(globalTimer);
setGlobalTimer(setTimeout(() => setPanelVisible(false), 5000));
setGlobalTimer(setTimeout(() => setPanelVisible(false), 2500));
}
}, [displayUI, isOpenContextMenu, state.isControlTouch, props.isPlay]);

View File

@ -170,9 +170,11 @@ public class FileDtoHelper : FileEntryDtoHelper
result.ThumbnailStatus = file.ThumbnailStatus;
var cacheKey = Math.Abs(result.Updated.GetHashCode());
if (file.ThumbnailStatus == Thumbnail.Created)
{
result.ThumbnailUrl = _commonLinkUtility.GetFullAbsolutePath(_filesLinkUtility.GetFileThumbnailUrl(file.Id, file.Version));
result.ThumbnailUrl = _commonLinkUtility.GetFullAbsolutePath(_filesLinkUtility.GetFileThumbnailUrl(file.Id, file.Version)) + $"&hash={cacheKey}";
}
}
catch (Exception)

View File

@ -177,10 +177,10 @@ public class RoomLogoManager
return new Logo
{
Original = await GetLogoPathAsync(id, SizeName.Original) + $"?_={cacheKey}",
Large = await GetLogoPathAsync(id, SizeName.Large) + $"?_={cacheKey}",
Medium = await GetLogoPathAsync(id, SizeName.Medium) + $"?_={cacheKey}",
Small = await GetLogoPathAsync(id, SizeName.Small) + $"?_={cacheKey}"
Original = await GetLogoPathAsync(id, SizeName.Original) + $"?hash={cacheKey}",
Large = await GetLogoPathAsync(id, SizeName.Large) + $"?hash={cacheKey}",
Medium = await GetLogoPathAsync(id, SizeName.Medium) + $"?hash={cacheKey}",
Small = await GetLogoPathAsync(id, SizeName.Small) + $"?hash={cacheKey}"
};
}

View File

@ -44,6 +44,7 @@ public class FilesChunkedUploadSessionHolder : CommonChunkedUploadSessionHolder
private async Task<T> InternalFinalizeAsync<T>(CommonChunkedUploadSession commonChunkedUploadSession)
{
var chunkedUploadSession = commonChunkedUploadSession as ChunkedUploadSession<T>;
chunkedUploadSession.BytesTotal = chunkedUploadSession.BytesUploaded;
var fileDao = GetFileDao<T>();
var file = await fileDao.FinalizeUploadSessionAsync(chunkedUploadSession);
return file.Id;

View File

@ -152,7 +152,7 @@ public class Builder<T>
catch (Exception exception)
{
_logger.ErrorBuildThumbnailsTenantId(fileData.TenantId, exception);
}
}
}
private async Task GenerateThumbnail(IFileDao<T> fileDao, FileData<T> fileData)
@ -188,6 +188,8 @@ public class Builder<T>
return;
}
await fileDao.SetThumbnailStatusAsync(file, Core.Thumbnail.Creating);
if (IsVideo(ext))
{
await MakeThumbnailFromVideo(fileDao, file);
@ -197,14 +199,16 @@ public class Builder<T>
if (IsImage(ext))
{
await CropImage(fileDao, file);
await MakeThumbnailFromImage(fileDao, file);
}
else
{
await MakeThumbnail(fileDao, file);
await MakeThumbnailFromDocs(fileDao, file);
}
}
await fileDao.SetThumbnailStatusAsync(file, Core.Thumbnail.Created);
var newFile = await fileDao.GetFileStableAsync(file.Id);
await _socketManager.UpdateFileAsync(newFile);
@ -244,10 +248,8 @@ public class Builder<T>
File.Delete(tempFilePath);
}
private async Task MakeThumbnail(IFileDao<T> fileDao, File<T> file)
private async Task MakeThumbnailFromDocs(IFileDao<T> fileDao, File<T> file)
{
await fileDao.SetThumbnailStatusAsync(file, Core.Thumbnail.Creating);
foreach (var w in _config.Sizes)
{
_logger.DebugMakeThumbnail1(file.Id.ToString());
@ -261,7 +263,7 @@ public class Builder<T>
try
{
(resultPercent, thumbnailUrl) = await GetThumbnailUrl(file, _global.DocThumbnailExtension.ToString(), w.Width, w.Height);
if (resultPercent == 100)
{
break;
@ -287,7 +289,7 @@ public class Builder<T>
{
_logger.WarningMakeThumbnail(file.Id.ToString(), thumbnailUrl, resultPercent, attempt, exception);
}
}
}
else
{
_logger.WarningMakeThumbnail(file.Id.ToString(), thumbnailUrl, resultPercent, attempt, exception);
@ -310,9 +312,6 @@ public class Builder<T>
await SaveThumbnail(fileDao, file, thumbnailUrl, w.Width, w.Height);
}
await fileDao.SetThumbnailStatusAsync(file, Core.Thumbnail.Created);
}
private async Task<(int, string)> GetThumbnailUrl(File<T> file, string toExtension, int width, int height)
@ -390,7 +389,7 @@ public class Builder<T>
return _fFmpegService.ExistFormat(extention);
}
private async Task CropImage(IFileDao<T> fileDao, File<T> file)
private async Task MakeThumbnailFromImage(IFileDao<T> fileDao, File<T> file)
{
_logger.DebugCropImage(file.Id.ToString());
@ -404,27 +403,11 @@ public class Builder<T>
private async Task Crop(IFileDao<T> fileDao, File<T> file, Stream stream)
{
using (var sourceImg = await Image.LoadAsync(stream))
using var sourceImg = await Image.LoadAsync(stream);
foreach (var w in _config.Sizes)
{
//var tasks = new List<Task>();
//foreach (var w in config.Sizes)
//{
// tasks.Add(CropAsync(sourceImg, fileDao, file, w.Width, w.Height));
//}
//await Task.WhenAll(tasks.ToArray());
//await Parallel.ForEachAsync(config.Sizes, (w, b) => CropAsync(sourceImg, fileDao, file, w.Width, w.Height));
await fileDao.SetThumbnailStatusAsync(file, Core.Thumbnail.Creating);
foreach (var w in _config.Sizes)
{
await CropAsync(sourceImg, fileDao, file, w.Width, w.Height);
}
await fileDao.SetThumbnailStatusAsync(file, Core.Thumbnail.Created);
await CropAsync(sourceImg, fileDao, file, w.Width, w.Height);
}
GC.Collect();
@ -432,7 +415,7 @@ public class Builder<T>
private async ValueTask CropAsync(Image sourceImg, IFileDao<T> fileDao, File<T> file, int width, int height)
{
using var targetImg = GetImageThumbnail(sourceImg, width);
using var targetImg = GetImageThumbnail(sourceImg, width, height);
using var targetStream = new MemoryStream();
switch (_global.ThumbnailExtension)
{
@ -462,11 +445,26 @@ public class Builder<T>
break;
}
await _globalStore.GetStore().SaveAsync(fileDao.GetUniqThumbnailPath(file, width, height), targetStream);
await _globalStore.GetStore().SaveAsync(fileDao.GetUniqThumbnailPath(file, width, height), targetStream);
}
private Image GetImageThumbnail(Image sourceBitmap, int thumbnaillWidth)
private Image GetImageThumbnail(Image sourceBitmap, int thumbnaillWidth, int thumbnaillHeight)
{
return sourceBitmap.Clone(x => x.BackgroundColor(Color.White).Resize(thumbnaillWidth, 0));
return sourceBitmap.Clone(x =>
{
var resizedImage = x.BackgroundColor(Color.White).Resize(thumbnaillWidth, 0);
var resizedImageWidth = resizedImage.GetCurrentSize().Width;
var resizedImageHeight = resizedImage.GetCurrentSize().Height;
var cropWidth = resizedImageWidth < thumbnaillWidth ? resizedImageWidth : thumbnaillWidth;
var cropHeight = resizedImageHeight < thumbnaillHeight ? resizedImageHeight : thumbnaillHeight;
if ((cropHeight != resizedImageHeight) || (cropWidth != resizedImageWidth))
{
x.Crop(cropWidth, cropHeight);
}
});
}
}

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": "没有解析邮件",