Merge branch 'develop' into feature/webhooks-ui

This commit is contained in:
Alexey Safronov 2023-02-16 14:36:38 +03:00
commit aa32773e61
166 changed files with 5283 additions and 3928 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

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

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

@ -49,20 +49,17 @@ export default function withContent(WrappedContent) {
render() {
const {
element,
isDesktop,
isTrashFolder,
isArchiveFolder,
item,
onFilesClick,
t,
viewer,
titleWithoutExt,
} = this.props;
const { access, createdBy, fileExst, fileStatus, href, icon, id } = item;
const { access, createdBy, fileStatus, href } = item;
const updatedDate = this.getStatusByDate(false);
const createdDate = this.getStatusByDate(true);

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

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

@ -11,6 +11,7 @@ import {
isMobileOnly,
isChrome,
isTablet,
isAndroid,
} from "react-device-detect";
import { inject, observer } from "mobx-react";
@ -130,6 +131,10 @@ const Layout = (props) => {
}
}
if (isMobileOnly && isAndroid && isChrome) {
height = `calc(100vh - ${correctorMobileChrome}px)`;
}
// if (isTablet && isIOS && isSafari) {
// if (
// window.innerHeight < window.innerWidth &&

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

@ -13,6 +13,7 @@ import config from "PACKAGE_FILE";
import { combineUrl, toUrlParams } from "@docspace/common/utils";
import { addFileToRecentlyViewed } from "@docspace/common/api/files";
import i18n from "./i18n";
import moment from "moment";
import { request } from "@docspace/common/api/client";
@ -265,3 +266,12 @@ export const connectedCloudsTypeIcon = (key) => {
default:
}
};
export const getDaysRemaining = (autoDelete) => {
let daysRemaining = moment(autoDelete)
.startOf("day")
.diff(moment().startOf("day"), "days");
if (daysRemaining <= 0) return "<1";
return "" + daysRemaining;
};

View File

@ -126,7 +126,7 @@ const AboutContent = (props) => {
</div>
<div className="row">
<Text className="row-el" fontSize="13px" noSelect>
<Text className="row-el" fontSize="13px">
{t("DocumentManagement")}:
</Text>
<ColorTheme
@ -138,6 +138,7 @@ const AboutContent = (props) => {
fontWeight="600"
href={linkRepo}
target="_blank"
enableUserSelect
>
&nbsp;ONLYOFFICE DocSpace&nbsp;
</ColorTheme>
@ -148,7 +149,7 @@ const AboutContent = (props) => {
</div>
<div className="row">
<Text className="row-el" fontSize="13px" noSelect>
<Text className="row-el" fontSize="13px">
{t("OnlineEditors")}:
</Text>
<ColorTheme
@ -160,6 +161,7 @@ const AboutContent = (props) => {
fontWeight="600"
href={linkDocs}
target="_blank"
enableUserSelect
>
&nbsp;ONLYOFFICE Docs&nbsp;
</ColorTheme>
@ -169,20 +171,20 @@ const AboutContent = (props) => {
</div>
<div className="row">
<Text className="row-el" fontSize="13px" noSelect>
<Text className="row-el" fontSize="13px">
{t("SoftwareLicense")}:{" "}
</Text>
<Text className="row-el" fontSize="13px" fontWeight="600" noSelect>
<Text className="row-el" fontSize="13px" fontWeight="600">
&nbsp;{license}
</Text>
</div>
<Text className="copyright" fontSize="14px" fontWeight="600" noSelect>
<Text className="copyright" fontSize="14px" fontWeight="600">
© {companyName}
</Text>
<div className="row">
<Text className="address-title" fontSize="13px" noSelect>
<Text className="address-title" fontSize="13px">
{t("AboutCompanyAddressTitle")}:{" "}
</Text>
<Text className="address-title select-el" fontSize="13px">
@ -191,7 +193,7 @@ const AboutContent = (props) => {
</div>
<div className="row">
<Text className="tel-title" fontSize="13px" noSelect>
<Text className="tel-title" fontSize="13px">
{t("Common:Phone")}:{" "}
</Text>
<Text className="tel-title select-el" fontSize="13px">
@ -200,7 +202,7 @@ const AboutContent = (props) => {
</div>
<div className="row">
<Text className="row-el" fontSize="13px" noSelect>
<Text className="row-el" fontSize="13px">
{t("AboutCompanyEmailTitle")}:
</Text>
@ -212,13 +214,14 @@ const AboutContent = (props) => {
fontSize="13px"
fontWeight="600"
href={`mailto:${companyInfoSettingsData.email}`}
enableUserSelect
>
&nbsp;{email}
</ColorTheme>
</div>
<div className="row">
<Text className="row-el" fontSize="13px" noSelect>
<Text className="row-el" fontSize="13px">
{t("Site")}:
</Text>
@ -231,6 +234,7 @@ const AboutContent = (props) => {
fontWeight="600"
target="_blank"
href={site}
enableUserSelect
>
&nbsp;{site.replace(/^https?\:\/\//i, "")}
</ColorTheme>

View File

@ -32,7 +32,6 @@ import { resendInvitesAgain } from "@docspace/common/api/people";
const StyledContainer = styled.div`
width: 100%;
min-height: 33px;
height: 69px;
.group-button-menu-container {
margin: 0 0 0 -20px;
@ -81,6 +80,7 @@ const StyledContainer = styled.div`
@media ${tablet} {
grid-template-columns: 1fr auto;
height: 60px;
}
${isMobile &&

View File

@ -3,6 +3,7 @@ import styled, { css } from "styled-components";
import { isIOS, isFirefox, isMobileOnly } from "react-device-detect";
import { inject, observer } from "mobx-react";
import { getBgPattern } from "@docspace/common/utils";
import { hugeMobile } from "@docspace/components/utils/device";
const StyledWrapper = styled.div`
height: ${isIOS && !isFirefox ? "calc(var(--vh, 1vh) * 100)" : "100vh"};
@ -18,17 +19,21 @@ const StyledWrapper = styled.div`
min-height: 100%;
width: 100%;
`}
`;
const BgBlock = styled.div`
background-image: ${(props) => props.bgPattern};
background-repeat: no-repeat;
background-attachment: fixed;
background-size: 100% 100%;
background-size: cover;
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: -1;
@media (max-width: 1024px) {
background-size: cover;
}
@media (max-width: 428px) {
@media ${hugeMobile} {
background-image: none;
}
`;
@ -37,7 +42,12 @@ const ConfirmWrapper = (props) => {
const { children, currentColorScheme } = props;
const bgPattern = getBgPattern(currentColorScheme?.id);
return <StyledWrapper bgPattern={bgPattern}>{children}</StyledWrapper>;
return (
<StyledWrapper>
<BgBlock bgPattern={bgPattern} />
{children}
</StyledWrapper>
);
};
export default inject(({ auth }) => {

View File

@ -1,12 +1,13 @@
import styled from "styled-components";
import { mobile, tablet } from "@docspace/components/utils/device";
import { hugeMobile, mobile, tablet } from "@docspace/components/utils/device";
export const StyledPage = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin: 56px auto 0 auto;
margin: 0 auto;
max-width: 960px;
box-sizing: border-box;
@media ${tablet} {
padding: 0 16px;
@ -30,6 +31,23 @@ export const StyledPage = styled.div`
}
`;
export const StyledContent = styled.div`
min-height: 100vh;
flex: 1 0 auto;
flex-direction: column;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
@media ${hugeMobile} {
justify-content: start;
min-height: 100%;
}
`;
export const StyledHeader = styled.div`
.title {
margin-bottom: 32px;
@ -44,7 +62,11 @@ export const StyledHeader = styled.div`
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 64px;
padding-bottom: 40px;
}
@media ${hugeMobile} {
margin-top: 0;
}
`;
@ -52,9 +74,27 @@ export const StyledBody = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin: 56px auto;
@media ${mobile} {
@media ${hugeMobile} {
width: 100%;
margin: 0 auto;
}
.title {
margin-bottom: 32px;
text-align: center;
}
.subtitle {
margin-bottom: 32px;
}
.docspace-logo {
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 40px;
}
.password-field-wrapper {

View File

@ -5,7 +5,6 @@ import Text from "@docspace/components/text";
import TextInput from "@docspace/components/text-input";
import PasswordInput from "@docspace/components/password-input";
import Button from "@docspace/components/button";
import Section from "@docspace/common/components/Section";
import FieldContainer from "@docspace/components/field-container";
import { inject, observer } from "mobx-react";
import { EmployeeActivationStatus } from "@docspace/common/constants";
@ -16,7 +15,12 @@ import {
} from "@docspace/common/api/people";
import { createPasswordHash } from "@docspace/common/utils";
import toastr from "@docspace/components/toast/toastr";
import { StyledPage, StyledBody, StyledHeader } from "./StyledConfirm";
import {
StyledPage,
StyledContent,
StyledBody,
StyledHeader,
} from "./StyledConfirm";
import withLoader from "../withLoader";
import { getPasswordErrorMessage } from "SRC_DIR/helpers/utils";
@ -135,123 +139,115 @@ const ActivateUserForm = (props) => {
return (
<StyledPage>
<StyledBody>
<StyledHeader>
<Text fontSize="23px" fontWeight="700" className="title">
{greetingTitle}
</Text>
<StyledContent>
<StyledBody>
<StyledHeader>
<Text fontSize="23px" fontWeight="700" className="title">
{greetingTitle}
</Text>
<Text className="subtitle">{t("InviteTitle")}</Text>
</StyledHeader>
<Text className="subtitle">{t("InviteTitle")}</Text>
</StyledHeader>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={!nameValid}
errorMessage={t("Common:RequiredField")}
>
<TextInput
id="name"
name="name"
value={name}
placeholder={t("Common:FirstName")}
size="large"
scale={true}
tabIndex={1}
isAutoFocussed={true}
autoComplete="given-name"
onChange={onChangeName}
onKeyDown={onKeyPress}
/>
</FieldContainer>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={!nameValid}
errorMessage={t("Common:RequiredField")}
>
<TextInput
id="name"
name="name"
value={name}
placeholder={t("Common:FirstName")}
size="large"
scale={true}
tabIndex={1}
isAutoFocussed={true}
autoComplete="given-name"
onChange={onChangeName}
onKeyDown={onKeyPress}
/>
</FieldContainer>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={!surNameValid}
errorMessage={t("Common:RequiredField")}
>
<TextInput
id="surname"
name="surname"
value={surName}
placeholder={t("Common:LastName")}
size="large"
scale={true}
tabIndex={2}
autoComplete="family-name"
onChange={onChangeSurName}
onKeyDown={onKeyPress}
/>
</FieldContainer>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={!surNameValid}
errorMessage={t("Common:RequiredField")}
>
<TextInput
id="surname"
name="surname"
value={surName}
placeholder={t("Common:LastName")}
size="large"
scale={true}
tabIndex={2}
autoComplete="family-name"
onChange={onChangeSurName}
onKeyDown={onKeyPress}
/>
</FieldContainer>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={isPasswordErrorShow && !passwordValid}
errorMessage={`${t(
"Common:PasswordLimitMessage"
)}: ${getPasswordErrorMessage(t, settings)}`}
>
<PasswordInput
className="confirm-input"
simpleView={false}
passwordSettings={settings}
id="password"
inputName="password"
placeholder={t("Common:Password")}
type="password"
inputValue={password}
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={isPasswordErrorShow && !passwordValid}
size="large"
scale={true}
tabIndex={1}
autoComplete="current-password"
onChange={onChangePassword}
onValidateInput={onValidatePassword}
onBlur={onBlurPassword}
onKeyDown={onKeyPress}
tooltipPasswordTitle={`${t("Common:PasswordLimitMessage")}:`}
tooltipPasswordLength={`${t("Common:PasswordMinimumLength")}: ${
settings ? settings.minLength : 8
}`}
tooltipPasswordDigits={`${t("Common:PasswordLimitDigits")}`}
tooltipPasswordCapital={`${t("Common:PasswordLimitUpperCase")}`}
tooltipPasswordSpecial={`${t(
"Common:PasswordLimitSpecialSymbols"
)}`}
generatePasswordTitle={t("Wizard:GeneratePassword")}
// If need copy credentials use t("EmailAndPasswordCopiedToClipboard")
errorMessage={`${t(
"Common:PasswordLimitMessage"
)}: ${getPasswordErrorMessage(t, settings)}`}
>
<PasswordInput
className="confirm-input"
simpleView={false}
passwordSettings={settings}
id="password"
inputName="password"
placeholder={t("Common:Password")}
type="password"
inputValue={password}
hasError={isPasswordErrorShow && !passwordValid}
size="large"
scale={true}
tabIndex={1}
autoComplete="current-password"
onChange={onChangePassword}
onValidateInput={onValidatePassword}
onBlur={onBlurPassword}
onKeyDown={onKeyPress}
tooltipPasswordTitle={`${t("Common:PasswordLimitMessage")}:`}
tooltipPasswordLength={`${t("Common:PasswordMinimumLength")}: ${
settings ? settings.minLength : 8
}`}
tooltipPasswordDigits={`${t("Common:PasswordLimitDigits")}`}
tooltipPasswordCapital={`${t("Common:PasswordLimitUpperCase")}`}
tooltipPasswordSpecial={`${t(
"Common:PasswordLimitSpecialSymbols"
)}`}
generatePasswordTitle={t("Wizard:GeneratePassword")}
// If need copy credentials use t("EmailAndPasswordCopiedToClipboard")
/>
</FieldContainer>
<Button
className="confirm-button"
primary
size="normal"
label={t("LoginRegistryButton")}
tabIndex={5}
onClick={onSubmit}
isDisabled={isLoading}
/>
</FieldContainer>
<Button
className="confirm-button"
primary
size="normal"
label={t("LoginRegistryButton")}
tabIndex={5}
onClick={onSubmit}
isDisabled={isLoading}
/>
</StyledBody>
</StyledBody>
</StyledContent>
</StyledPage>
);
};
const ActivateUserFormWrapper = (props) => {
return (
<Section>
<Section.SectionBody>
<ActivateUserForm {...props} />
</Section.SectionBody>
</Section>
);
};
export default inject(({ auth }) => {
const {
greetingSettings,
@ -272,7 +268,7 @@ export default inject(({ auth }) => {
})(
withRouter(
withTranslation(["Confirm", "Common", "Wizard"])(
withLoader(observer(ActivateUserFormWrapper))
withLoader(observer(ActivateUserForm))
)
)
);

View File

@ -3,13 +3,12 @@ import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import Text from "@docspace/components/text";
import Button from "@docspace/components/button";
import Section from "@docspace/common/components/Section";
import { inject, observer } from "mobx-react";
import {
StyledPage,
StyledBody,
StyledHeader,
ButtonsWrapper,
StyledContent,
} from "./StyledConfirm";
import withLoader from "../withLoader";
import FormWrapper from "@docspace/components/form-wrapper";
@ -20,60 +19,50 @@ const ChangeOwnerForm = (props) => {
console.log(props.linkData);
return (
<StyledPage>
<StyledBody>
<StyledHeader>
<StyledContent>
<StyledBody>
<DocspaceLogo className="docspace-logo" />
<Text fontSize="23px" fontWeight="700" className="title">
{greetingTitle}
</Text>
</StyledHeader>
<FormWrapper>
<Text className="subtitle">
{t("ConfirmOwnerPortalTitle", { newOwner: "NEW OWNER" })}
</Text>
<ButtonsWrapper>
<Button
primary
scale
size="medium"
label={t("Common:SaveButton")}
tabIndex={2}
isDisabled={false}
//onClick={this.onAcceptClick} // call toast with t("ConfirmOwnerPortalSuccessMessage")
/>
<Button
scale
size="medium"
label={t("Common:CancelButton")}
tabIndex={2}
isDisabled={false}
//onClick={this.onCancelClick}
/>
</ButtonsWrapper>
</FormWrapper>
</StyledBody>
<FormWrapper>
<Text className="subtitle">
{t("ConfirmOwnerPortalTitle", { newOwner: "NEW OWNER" })}
</Text>
<ButtonsWrapper>
<Button
primary
scale
size="medium"
label={t("Common:SaveButton")}
tabIndex={2}
isDisabled={false}
//onClick={this.onAcceptClick} // call toast with t("ConfirmOwnerPortalSuccessMessage")
/>
<Button
scale
size="medium"
label={t("Common:CancelButton")}
tabIndex={2}
isDisabled={false}
//onClick={this.onCancelClick}
/>
</ButtonsWrapper>
</FormWrapper>
</StyledBody>
</StyledContent>
</StyledPage>
);
};
const ChangeOwnerFormWrapper = (props) => {
return (
<Section>
<Section.SectionBody>
<ChangeOwnerForm {...props} />
</Section.SectionBody>
</Section>
);
};
export default inject(({ auth }) => ({
greetingTitle: auth.settingsStore.greetingSettings,
defaultPage: auth.settingsStore.defaultPage,
}))(
withRouter(
withTranslation(["Confirm", "Common"])(
withLoader(observer(ChangeOwnerFormWrapper))
withLoader(observer(ChangeOwnerForm))
)
)
);

View File

@ -1,13 +1,12 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import Text from "@docspace/components/text";
import PasswordInput from "@docspace/components/password-input";
import Button from "@docspace/components/button";
import Section from "@docspace/common/components/Section";
import FieldContainer from "@docspace/components/field-container";
import { inject, observer } from "mobx-react";
import { StyledPage, StyledBody, StyledHeader } from "./StyledConfirm";
import { StyledPage, StyledBody, StyledContent } from "./StyledConfirm";
import withLoader from "../withLoader";
import { getPasswordErrorMessage } from "../../../helpers/utils";
import { createPasswordHash } from "@docspace/common/utils";
@ -26,6 +25,7 @@ const ChangePasswordForm = (props) => {
logout,
changePassword,
linkData,
getSettings,
} = props;
const [password, setPassword] = useState("");
@ -33,6 +33,10 @@ const ChangePasswordForm = (props) => {
const [isPasswordErrorShow, setIsPasswordErrorShow] = useState(false);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (!hashSettings) getSettings(true);
}, []);
const onChangePassword = (e) => {
setPassword(e.target.value);
};
@ -45,7 +49,7 @@ const ChangePasswordForm = (props) => {
setIsPasswordErrorShow(true);
};
const onSubmit = () => {
const onSubmit = async () => {
setIsLoading(true);
if (!password.trim()) {
@ -57,30 +61,34 @@ const ChangePasswordForm = (props) => {
return;
}
const hash = createPasswordHash(password, hashSettings);
const { uid, confirmHeader } = linkData;
changePassword(uid, hash, confirmHeader)
.then(() => logout())
.then(() => {
setIsLoading(false);
toastr.success(t("ChangePasswordSuccess"));
tryRedirectTo(defaultPage);
})
.catch((error) => {
let errorMessage = "";
if (typeof error === "object") {
errorMessage =
error?.response?.data?.error?.message ||
error?.statusText ||
error?.message ||
"";
} else {
errorMessage = error;
}
try {
const hash = createPasswordHash(password, hashSettings);
const { uid, confirmHeader } = linkData;
await changePassword(uid, hash, confirmHeader);
logout();
setIsLoading(false);
toastr.success(t("ChangePasswordSuccess"));
tryRedirectTo(defaultPage);
} catch (error) {
let errorMessage = "";
if (typeof error === "object") {
errorMessage =
error?.response?.data?.error?.message ||
error?.statusText ||
error?.message ||
"";
} else {
errorMessage = error;
}
console.error(errorMessage);
if (errorMessage === "Invalid params") {
toastr.error(t("Common:SomethingWentWrong"));
} else {
toastr.error(t(`${errorMessage}`));
setIsLoading(false);
});
}
setIsLoading(false);
}
};
const onKeyPress = (event) => {
@ -91,83 +99,75 @@ const ChangePasswordForm = (props) => {
return (
<StyledPage>
<StyledBody>
<StyledHeader>
<StyledContent>
<StyledBody>
<DocspaceLogo className="docspace-logo" />
<Text fontSize="23px" fontWeight="700" className="title">
{greetingTitle}
</Text>
</StyledHeader>
<FormWrapper>
<div className="password-form">
<Text fontSize="16px" fontWeight="600" className="subtitle">
{t("PassworResetTitle")}
</Text>
<FieldContainer
isVertical={true}
labelVisible={false}
hasError={isPasswordErrorShow && !passwordValid}
errorMessage={`${t(
"Common:PasswordLimitMessage"
)}: ${getPasswordErrorMessage(t, settings)}`}
>
<PasswordInput
simpleView={false}
passwordSettings={settings}
id="password"
inputName="password"
placeholder={t("Common:Password")}
type="password"
inputValue={password}
<FormWrapper>
<div className="password-form">
<Text fontSize="16px" fontWeight="600" className="subtitle">
{t("PassworResetTitle")}
</Text>
<FieldContainer
isVertical={true}
labelVisible={false}
hasError={isPasswordErrorShow && !passwordValid}
size="large"
scale
tabIndex={1}
autoComplete="current-password"
onChange={onChangePassword}
onValidateInput={onValidatePassword}
onBlur={onBlurPassword}
onKeyDown={onKeyPress}
tooltipPasswordTitle={`${t("Common:PasswordLimitMessage")}:`}
tooltipPasswordLength={`${t("Common:PasswordMinimumLength")}: ${
settings ? settings.minLength : 8
}`}
tooltipPasswordDigits={`${t("Common:PasswordLimitDigits")}`}
tooltipPasswordCapital={`${t("Common:PasswordLimitUpperCase")}`}
tooltipPasswordSpecial={`${t(
"Common:PasswordLimitSpecialSymbols"
)}`}
generatePasswordTitle={t("Wizard:GeneratePassword")}
/>
</FieldContainer>
</div>
errorMessage={`${t(
"Common:PasswordLimitMessage"
)}: ${getPasswordErrorMessage(t, settings)}`}
>
<PasswordInput
simpleView={false}
passwordSettings={settings}
id="password"
inputName="password"
placeholder={t("Common:Password")}
type="password"
inputValue={password}
hasError={isPasswordErrorShow && !passwordValid}
size="large"
scale
tabIndex={1}
autoComplete="current-password"
onChange={onChangePassword}
onValidateInput={onValidatePassword}
onBlur={onBlurPassword}
onKeyDown={onKeyPress}
tooltipPasswordTitle={`${t("Common:PasswordLimitMessage")}:`}
tooltipPasswordLength={`${t(
"Common:PasswordMinimumLength"
)}: ${settings ? settings.minLength : 8}`}
tooltipPasswordDigits={`${t("Common:PasswordLimitDigits")}`}
tooltipPasswordCapital={`${t(
"Common:PasswordLimitUpperCase"
)}`}
tooltipPasswordSpecial={`${t(
"Common:PasswordLimitSpecialSymbols"
)}`}
generatePasswordTitle={t("Wizard:GeneratePassword")}
/>
</FieldContainer>
</div>
<Button
primary
size="medium"
scale
label={t("Common:Create")}
tabIndex={5}
onClick={onSubmit}
isDisabled={isLoading}
/>
</FormWrapper>
</StyledBody>
<Button
primary
size="medium"
scale
label={t("Common:Create")}
tabIndex={5}
onClick={onSubmit}
isDisabled={isLoading}
/>
</FormWrapper>
</StyledBody>
</StyledContent>
</StyledPage>
);
};
const ChangePasswordFormWrapper = (props) => {
return (
<Section>
<Section.SectionBody>
<ChangePasswordForm {...props} />
</Section.SectionBody>
</Section>
);
};
export default inject(({ auth, setup }) => {
const {
greetingSettings,
@ -175,6 +175,7 @@ export default inject(({ auth, setup }) => {
defaultPage,
passwordSettings,
theme,
getSettings,
} = auth.settingsStore;
const { changePassword } = setup;
@ -187,11 +188,12 @@ export default inject(({ auth, setup }) => {
logout: auth.logout,
changePassword,
isAuthenticated: auth.isAuthenticated,
getSettings,
};
})(
withRouter(
withTranslation(["Confirm", "Common", "Wizard"])(
withLoader(observer(ChangePasswordFormWrapper))
withLoader(observer(ChangePasswordForm))
)
)
);

View File

@ -4,9 +4,8 @@ import { withTranslation } from "react-i18next";
import Text from "@docspace/components/text";
import TextInput from "@docspace/components/text-input";
import Button from "@docspace/components/button";
import Section from "@docspace/common/components/Section";
import { inject, observer } from "mobx-react";
import { StyledPage, StyledBody, StyledHeader } from "./StyledConfirm";
import { StyledPage, StyledBody, StyledContent } from "./StyledConfirm";
import withLoader from "../withLoader";
import FormWrapper from "@docspace/components/form-wrapper";
import DocspaceLogo from "../../../DocspaceLogo";
@ -17,66 +16,54 @@ const ChangePhoneForm = (props) => {
return (
<StyledPage>
<StyledBody>
<StyledHeader>
<StyledContent>
<StyledBody>
<DocspaceLogo className="docspace-logo" />
<Text fontSize="23px" fontWeight="700" className="title">
{greetingTitle}
</Text>
</StyledHeader>
<FormWrapper>
<div className="subtitle">
<Text fontSize="16px" fontWeight="600" className="phone-title">
{t("EnterPhone")}
</Text>
<Text>
{t("CurrentNumber")}: {currentNumber}
</Text>
<Text>{t("PhoneSubtitle")}</Text>
</div>
<FormWrapper>
<div className="subtitle">
<Text fontSize="16px" fontWeight="600" className="phone-title">
{t("EnterPhone")}
</Text>
<Text>
{t("CurrentNumber")}: {currentNumber}
</Text>
<Text>{t("PhoneSubtitle")}</Text>
</div>
<TextInput
className="phone-input"
id="phone"
name="phone"
type="phone"
size="large"
scale={true}
isAutoFocussed={true}
tabIndex={1}
hasError={false}
guide={false}
/>
<TextInput
className="phone-input"
id="phone"
name="phone"
type="phone"
size="large"
scale={true}
isAutoFocussed={true}
tabIndex={1}
hasError={false}
guide={false}
/>
<Button
primary
scale
size="medium"
label={t("GetCode")}
tabIndex={2}
isDisabled={false}
/>
</FormWrapper>
</StyledBody>
<Button
primary
scale
size="medium"
label={t("GetCode")}
tabIndex={2}
isDisabled={false}
/>
</FormWrapper>
</StyledBody>
</StyledContent>
</StyledPage>
);
};
const ChangePhoneFormWrapper = (props) => {
return (
<Section>
<Section.SectionBody>
<ChangePhoneForm {...props} />
</Section.SectionBody>
</Section>
);
};
export default inject(({ auth }) => ({
greetingTitle: auth.settingsStore.greetingSettings,
}))(
withRouter(
withTranslation("Confirm")(withLoader(observer(ChangePhoneFormWrapper)))
)
withRouter(withTranslation("Confirm")(withLoader(observer(ChangePhoneForm))))
);

View File

@ -2,7 +2,6 @@ import React, { useState } from "react";
import { withRouter } from "react-router";
import { Trans, withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import Section from "@docspace/common/components/Section";
import Text from "@docspace/components/text";
import Button from "@docspace/components/button";
import Link from "@docspace/components/link";
@ -11,7 +10,7 @@ import { continuePortal } from "@docspace/common/api/portal";
import {
StyledPage,
StyledBody,
StyledHeader,
StyledContent,
ButtonsWrapper,
} from "./StyledConfirm";
@ -40,68 +39,56 @@ const ContinuePortal = (props) => {
return (
<StyledPage>
<StyledBody>
<StyledHeader>
<StyledContent>
<StyledBody>
<DocspaceLogo className="docspace-logo" />
<Text fontSize="23px" fontWeight="700" className="title">
{greetingTitle}
</Text>
</StyledHeader>
<FormWrapper>
{isReactivate ? (
<Trans t={t} i18nKey="SuccessReactivate" ns="Confirm">
Your account has been successfully reactivated. In 10 seconds you
will be redirected to the
<Link isHovered href="/">
portal
</Link>
</Trans>
) : (
<>
<Text className="subtitle">{t("PortalContinueTitle")}</Text>
<ButtonsWrapper>
<Button
primary
scale
size="medium"
label={t("Reactivate")}
tabIndex={1}
onClick={onRestoreClick}
/>
<Button
scale
size="medium"
label={t("Common:CancelButton")}
tabIndex={1}
onClick={onCancelClick}
/>
</ButtonsWrapper>
</>
)}
</FormWrapper>
</StyledBody>
<FormWrapper>
{isReactivate ? (
<Trans t={t} i18nKey="SuccessReactivate" ns="Confirm">
Your account has been successfully reactivated. In 10 seconds
you will be redirected to the
<Link isHovered href="/">
portal
</Link>
</Trans>
) : (
<>
<Text className="subtitle">{t("PortalContinueTitle")}</Text>
<ButtonsWrapper>
<Button
primary
scale
size="medium"
label={t("Reactivate")}
tabIndex={1}
onClick={onRestoreClick}
/>
<Button
scale
size="medium"
label={t("Common:CancelButton")}
tabIndex={1}
onClick={onCancelClick}
/>
</ButtonsWrapper>
</>
)}
</FormWrapper>
</StyledBody>
</StyledContent>
</StyledPage>
);
};
const ContinuePortalWrapper = (props) => {
return (
<Section>
<Section.SectionBody>
<ContinuePortal {...props} />
</Section.SectionBody>
</Section>
);
};
export default inject(({ auth }) => ({
greetingTitle: auth.settingsStore.greetingSettings,
theme: auth.settingsStore.theme,
}))(
withRouter(
withTranslation(["Confirm", "Common"])(
withLoader(observer(ContinuePortalWrapper))
)
withTranslation(["Confirm", "Common"])(withLoader(observer(ContinuePortal)))
)
);

View File

@ -16,7 +16,6 @@ import FieldContainer from "@docspace/components/field-container";
import toastr from "@docspace/components/toast/toastr";
import SocialButton from "@docspace/components/social-button";
import { getUserFromConfirm } from "@docspace/common/api/people";
import Section from "@docspace/common/components/Section";
import {
createPasswordHash,
getProviderTranslation,
@ -33,6 +32,7 @@ import FormWrapper from "@docspace/components/form-wrapper";
import DocspaceLogo from "../../../DocspaceLogo";
import Box from "@docspace/components/box";
import DefaultUserPhoto from "PUBLIC_DIR/images/default_user_photo_size_82-82.png";
import { StyledPage, StyledContent } from "./StyledConfirm";
export const ButtonsWrapper = styled.div`
display: flex;
@ -46,7 +46,7 @@ export const ButtonsWrapper = styled.div`
`;
const ConfirmContainer = styled(Box)`
margin-top: 80px;
margin: 56px auto;
display: flex;
flex: 1fr 1fr;
gap: 80px;
@ -54,7 +54,6 @@ const ConfirmContainer = styled(Box)`
justify-content: center;
@media ${tablet} {
margin: 100px auto 0 auto;
display: flex;
flex: 1fr;
flex-direction: column;
@ -63,7 +62,7 @@ const ConfirmContainer = styled(Box)`
}
@media ${hugeMobile} {
margin-top: 32px;
margin: 0 auto;
width: 100%;
flex: 1fr;
flex-direction: column;
@ -132,6 +131,7 @@ const GreetingContainer = styled.div`
}
.docspace-logo {
width: 100%;
padding-bottom: 32px;
.injected-svg {
@ -142,7 +142,7 @@ const GreetingContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 64px;
padding-bottom: 40px;
}
}
`;
@ -187,12 +187,12 @@ const RegisterContainer = styled.div`
//margin-top: 32px;
width: 100%;
@media (max-width: 768px) {
margin: 32px 0 0 0;
@media ${tablet} {
//margin: 32px 0 0 0;
width: 100%;
}
@media (max-width: 375px) {
margin: 32px 0 0 0;
@media ${hugeMobile} {
//margin: 32px 0 0 0;
width: 100%;
}
}
@ -211,10 +211,9 @@ const RegisterContainer = styled.div`
.password-field-wrapper {
width: 100%;
}
}
`;
}`;
const Confirm = (props) => {
const CreateUserForm = (props) => {
const { settings, t, greetingTitle, providers, isDesktop, linkData } = props;
const inputRef = React.useRef(null);
@ -553,233 +552,238 @@ const Confirm = (props) => {
const userAvatar = user.hasAvatar ? user.avatar : DefaultUserPhoto;
return (
<ConfirmContainer>
<GreetingContainer isGreetingMode={isGreetingMode}>
<DocspaceLogo className="docspace-logo" />
<Text
fontSize="23px"
fontWeight={700}
textAlign="left"
className="greeting-title"
>
{greetingTitle}
</Text>
<div className="greeting-block">
<Avatar className="avatar" role="user" source={user.avatar} />
<div className="user-info">
<Text fontSize="15px" fontWeight={600}>
{user.firstName} {user.lastName}
<StyledPage>
<StyledContent>
<ConfirmContainer>
<GreetingContainer isGreetingMode={isGreetingMode}>
<DocspaceLogo className="docspace-logo" />
<Text
fontSize="23px"
fontWeight={700}
textAlign="left"
className="greeting-title"
>
{greetingTitle}
</Text>
<Text fontSize="12px" fontWeight={600} color="#A3A9AE">
{user.department}
</Text>
</div>
</div>
<div className="tooltip">
<span className="tooltiptext">{t("WelcomeUser")}</span>
</div>
</GreetingContainer>
<div className="greeting-block">
<Avatar className="avatar" role="user" source={user.avatar} />
<div className="user-info">
<Text fontSize="15px" fontWeight={600}>
{user.firstName} {user.lastName}
</Text>
<Text fontSize="12px" fontWeight={600} color="#A3A9AE">
{user.department}
</Text>
</div>
</div>
<FormWrapper>
<RegisterContainer isGreetingMode={isGreetingMode}>
{ssoExists() && <ButtonsWrapper>{ssoButton()}</ButtonsWrapper>}
<div className="tooltip">
<span className="tooltiptext">{t("WelcomeUser")}</span>
</div>
</GreetingContainer>
{oauthDataExists() && (
<>
<ButtonsWrapper>{providerButtons()}</ButtonsWrapper>
{providers && providers.length > 2 && (
<Link
isHovered
type="action"
fontSize="13px"
fontWeight="600"
color="#3B72A7"
className="more-label"
onClick={moreAuthOpen}
>
{t("Common:ShowMore")}
</Link>
<FormWrapper>
<RegisterContainer isGreetingMode={isGreetingMode}>
{ssoExists() && <ButtonsWrapper>{ssoButton()}</ButtonsWrapper>}
{oauthDataExists() && (
<>
<ButtonsWrapper>{providerButtons()}</ButtonsWrapper>
{providers && providers.length > 2 && (
<Link
isHovered
type="action"
fontSize="13px"
fontWeight="600"
color="#3B72A7"
className="more-label"
onClick={moreAuthOpen}
>
{t("Common:ShowMore")}
</Link>
)}
</>
)}
</>
)}
{(oauthDataExists() || ssoExists()) && (
<div className="line">
<Text color="#A3A9AE" className="or-label">
{t("Common:Or")}
</Text>
</div>
)}
{(oauthDataExists() || ssoExists()) && (
<div className="line">
<Text color="#A3A9AE" className="or-label">
{t("Common:Or")}
</Text>
</div>
)}
<form className="auth-form-container">
<div className="auth-form-fields">
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={isEmailErrorShow && !emailValid}
errorMessage={
emailErrorText
? t(`Common:${emailErrorText}`)
: t("Common:RequiredField")
}
>
<EmailInput
id="login"
name="login"
type="email"
hasError={isEmailErrorShow && !emailValid}
value={email}
placeholder={t("Common:Email")}
size="large"
scale={true}
isAutoFocussed={true}
tabIndex={1}
isDisabled={isLoading || !!emailFromLink}
autoComplete="username"
onChange={onChangeEmail}
onBlur={onBlurEmail}
onValidateInput={onValidateEmail}
forwardedRef={inputRef}
/>
</FieldContainer>
<form className="auth-form-container">
<div className="auth-form-fields">
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={isEmailErrorShow && !emailValid}
errorMessage={
emailErrorText
? t(`Common:${emailErrorText}`)
: t("Common:RequiredField")
}
>
<EmailInput
id="login"
name="login"
type="email"
hasError={isEmailErrorShow && !emailValid}
value={email}
placeholder={t("Common:Email")}
size="large"
scale={true}
isAutoFocussed={true}
tabIndex={1}
isDisabled={isLoading || !!emailFromLink}
autoComplete="username"
onChange={onChangeEmail}
onBlur={onBlurEmail}
onValidateInput={onValidateEmail}
forwardedRef={inputRef}
/>
</FieldContainer>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={!fnameValid}
errorMessage={errorText ? errorText : t("Common:RequiredField")}
>
<TextInput
id="first-name"
name="first-name"
type="text"
hasError={!fnameValid}
value={fname}
placeholder={t("Common:FirstName")}
size="large"
scale={true}
tabIndex={1}
isDisabled={isLoading}
onChange={onChangeFname}
onKeyDown={onKeyPress}
/>
</FieldContainer>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={!fnameValid}
errorMessage={
errorText ? errorText : t("Common:RequiredField")
}
>
<TextInput
id="first-name"
name="first-name"
type="text"
hasError={!fnameValid}
value={fname}
placeholder={t("Common:FirstName")}
size="large"
scale={true}
tabIndex={1}
isDisabled={isLoading}
onChange={onChangeFname}
onKeyDown={onKeyPress}
/>
</FieldContainer>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={!snameValid}
errorMessage={errorText ? errorText : t("Common:RequiredField")}
>
<TextInput
id="last-name"
name="last-name"
type="text"
hasError={!snameValid}
value={sname}
placeholder={t("Common:LastName")}
size="large"
scale={true}
tabIndex={1}
isDisabled={isLoading}
onChange={onChangeSname}
onKeyDown={onKeyPress}
/>
</FieldContainer>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={!snameValid}
errorMessage={
errorText ? errorText : t("Common:RequiredField")
}
>
<TextInput
id="last-name"
name="last-name"
type="text"
hasError={!snameValid}
value={sname}
placeholder={t("Common:LastName")}
size="large"
scale={true}
tabIndex={1}
isDisabled={isLoading}
onChange={onChangeSname}
onKeyDown={onKeyPress}
/>
</FieldContainer>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={isPasswordErrorShow && !passwordValid}
errorMessage={`${t(
"Common:PasswordLimitMessage"
)}: ${getPasswordErrorMessage(t, settings)}`}
>
<PasswordInput
simpleView={false}
hideNewPasswordButton
showCopyLink={false}
passwordSettings={settings}
id="password"
inputName="password"
placeholder={t("Common:Password")}
type="password"
hasError={isPasswordErrorShow && !passwordValid}
inputValue={password}
size="large"
scale={true}
tabIndex={1}
isDisabled={isLoading}
autoComplete="current-password"
onChange={onChangePassword}
onBlur={onBlurPassword}
onKeyDown={onKeyPress}
onValidateInput={onValidatePassword}
tooltipPasswordTitle={`${t("Common:PasswordLimitMessage")}:`}
tooltipPasswordLength={`${t(
"Common:PasswordMinimumLength"
)}: ${settings ? settings.minLength : 8}`}
tooltipPasswordDigits={`${t("Common:PasswordLimitDigits")}`}
tooltipPasswordCapital={`${t(
"Common:PasswordLimitUpperCase"
)}`}
tooltipPasswordSpecial={`${t(
"Common:PasswordLimitSpecialSymbols"
)}`}
generatePasswordTitle={t("Wizard:GeneratePassword")}
/>
</FieldContainer>
<FieldContainer
className="form-field"
isVertical={true}
labelVisible={false}
hasError={isPasswordErrorShow && !passwordValid}
errorMessage={`${t(
"Common:PasswordLimitMessage"
)}: ${getPasswordErrorMessage(t, settings)}`}
>
<PasswordInput
simpleView={false}
hideNewPasswordButton
showCopyLink={false}
passwordSettings={settings}
id="password"
inputName="password"
placeholder={t("Common:Password")}
type="password"
hasError={isPasswordErrorShow && !passwordValid}
inputValue={password}
size="large"
scale={true}
tabIndex={1}
isDisabled={isLoading}
autoComplete="current-password"
onChange={onChangePassword}
onBlur={onBlurPassword}
onKeyDown={onKeyPress}
onValidateInput={onValidatePassword}
tooltipPasswordTitle={`${t(
"Common:PasswordLimitMessage"
)}:`}
tooltipPasswordLength={`${t(
"Common:PasswordMinimumLength"
)}: ${settings ? settings.minLength : 8}`}
tooltipPasswordDigits={`${t(
"Common:PasswordLimitDigits"
)}`}
tooltipPasswordCapital={`${t(
"Common:PasswordLimitUpperCase"
)}`}
tooltipPasswordSpecial={`${t(
"Common:PasswordLimitSpecialSymbols"
)}`}
generatePasswordTitle={t("Wizard:GeneratePassword")}
/>
</FieldContainer>
<Button
className="login-button"
primary
size="medium"
scale={true}
label={
isLoading
? t("Common:LoadingProcessing")
: t("LoginRegistryButton")
}
tabIndex={1}
isDisabled={isLoading}
isLoading={isLoading}
onClick={onSubmit}
<Button
className="login-button"
primary
size="medium"
scale={true}
label={
isLoading
? t("Common:LoadingProcessing")
: t("LoginRegistryButton")
}
tabIndex={1}
isDisabled={isLoading}
isLoading={isLoading}
onClick={onSubmit}
/>
</div>
</form>
<MoreLoginModal
t={t}
visible={moreAuthVisible}
onClose={moreAuthClose}
providers={providers}
onSocialLoginClick={onSocialButtonClick}
ssoLabel={ssoLabel}
ssoUrl={ssoUrl}
/>
</div>
</form>
<MoreLoginModal
t={t}
visible={moreAuthVisible}
onClose={moreAuthClose}
providers={providers}
onSocialLoginClick={onSocialButtonClick}
ssoLabel={ssoLabel}
ssoUrl={ssoUrl}
/>
</RegisterContainer>
</FormWrapper>
</ConfirmContainer>
</RegisterContainer>
</FormWrapper>
</ConfirmContainer>
</StyledContent>
</StyledPage>
);
};
Confirm.propTypes = {
CreateUserForm.propTypes = {
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
};
const CreateUserForm = (props) => (
<Section>
<Section.SectionBody>
<Confirm {...props} />
</Section.SectionBody>
</Section>
);
export default inject(({ auth }) => {
const {

View File

@ -2,7 +2,6 @@ import React, { useState } from "react";
import { withRouter } from "react-router";
import { Trans, withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import Section from "@docspace/common/components/Section";
import Text from "@docspace/components/text";
import Button from "@docspace/components/button";
import Link from "@docspace/components/link";
@ -11,7 +10,7 @@ import { suspendPortal } from "@docspace/common/api/portal";
import {
StyledPage,
StyledBody,
StyledHeader,
StyledContent,
ButtonsWrapper,
} from "./StyledConfirm";
@ -50,61 +49,51 @@ const DeactivatePortal = (props) => {
return (
<StyledPage>
<StyledBody>
<StyledHeader>
<StyledContent>
<StyledBody>
<DocspaceLogo className="docspace-logo" />
<Text fontSize="23px" fontWeight="700" className="title">
{greetingTitle}
</Text>
</StyledHeader>
<FormWrapper>
{isDeactivate ? (
<Trans t={t} i18nKey="SuccessDeactivate" ns="Confirm">
Your account has been successfully deactivated. In 10 seconds you
will be redirected to the
<Link isHovered href={url}>
site
</Link>
</Trans>
) : (
<>
<Text className="subtitle">{t("PortalDeactivateTitle")}</Text>
<ButtonsWrapper>
<Button
scale
primary
size="medium"
label={t("Settings:Deactivate")}
tabIndex={1}
onClick={onDeactivateClick}
/>
<Button
scale
size="medium"
label={t("Common:CancelButton")}
tabIndex={1}
onClick={onCancelClick}
/>
</ButtonsWrapper>
</>
)}
</FormWrapper>
</StyledBody>
<FormWrapper>
{isDeactivate ? (
<Trans t={t} i18nKey="SuccessDeactivate" ns="Confirm">
Your account has been successfully deactivated. In 10 seconds
you will be redirected to the
<Link isHovered href={url}>
site
</Link>
</Trans>
) : (
<>
<Text className="subtitle">{t("PortalDeactivateTitle")}</Text>
<ButtonsWrapper>
<Button
scale
primary
size="medium"
label={t("Settings:Deactivate")}
tabIndex={1}
onClick={onDeactivateClick}
/>
<Button
scale
size="medium"
label={t("Common:CancelButton")}
tabIndex={1}
onClick={onCancelClick}
/>
</ButtonsWrapper>
</>
)}
</FormWrapper>
</StyledBody>
</StyledContent>
</StyledPage>
);
};
const DeactivatePortalWrapper = (props) => {
return (
<Section>
<Section.SectionBody>
<DeactivatePortal {...props} />
</Section.SectionBody>
</Section>
);
};
export default inject(({ auth }) => ({
greetingTitle: auth.settingsStore.greetingSettings,
theme: auth.settingsStore.theme,
@ -112,7 +101,7 @@ export default inject(({ auth }) => ({
}))(
withRouter(
withTranslation(["Confirm", "Settings", "Common"])(
withLoader(observer(DeactivatePortalWrapper))
withLoader(observer(DeactivatePortal))
)
)
);

View File

@ -7,7 +7,7 @@ import Section from "@docspace/common/components/Section";
import { inject, observer } from "mobx-react";
import { deleteSelf } from "@docspace/common/api/people";
import toastr from "@docspace/components/toast/toastr";
import { StyledPage, StyledBody, StyledHeader } from "./StyledConfirm";
import { StyledPage, StyledBody, StyledContent } from "./StyledConfirm";
import withLoader from "../withLoader";
import FormWrapper from "@docspace/components/form-wrapper";
import DocspaceLogo from "../../../DocspaceLogo";
@ -35,8 +35,8 @@ const ProfileRemoveForm = (props) => {
if (isProfileDeleted) {
return (
<StyledPage>
<StyledBody>
<StyledHeader>
<StyledContent>
<StyledBody>
<DocspaceLogo className="docspace-logo" />
<Text fontSize="23px" fontWeight="700" className="title">
{t("DeleteProfileSuccessMessage")}
@ -44,65 +44,55 @@ const ProfileRemoveForm = (props) => {
<Text fontSize="16px" fontWeight="600" className="confirm-subtitle">
{t("DeleteProfileSuccessMessageInfo")}
</Text>
</StyledHeader>
</StyledBody>
</StyledBody>
</StyledContent>
</StyledPage>
);
}
return (
<StyledPage>
<StyledBody>
<StyledHeader>
<StyledContent>
<StyledBody>
<DocspaceLogo className="docspace-logo" />
<Text fontSize="23px" fontWeight="700" className="title">
{greetingTitle}
</Text>
</StyledHeader>
<FormWrapper>
<div className="subtitle">
<Text
fontSize="16px"
fontWeight="600"
className="delete-profile-confirm"
>
{t("DeleteProfileConfirmation")}
</Text>
<Text>{t("DeleteProfileConfirmationInfo")}</Text>
</div>
<FormWrapper>
<div className="subtitle">
<Text
fontSize="16px"
fontWeight="600"
className="delete-profile-confirm"
>
{t("DeleteProfileConfirmation")}
</Text>
<Text>{t("DeleteProfileConfirmationInfo")}</Text>
</div>
<Button
primary
scale
size="medium"
label={t("DeleteProfileBtn")}
tabIndex={1}
isDisabled={isLoading}
onClick={onDeleteProfile}
/>
</FormWrapper>
</StyledBody>
<Button
primary
scale
size="medium"
label={t("DeleteProfileBtn")}
tabIndex={1}
isDisabled={isLoading}
onClick={onDeleteProfile}
/>
</FormWrapper>
</StyledBody>
</StyledContent>
</StyledPage>
);
};
const ProfileRemoveFormWrapper = (props) => {
return (
<Section>
<Section.SectionBody>
<ProfileRemoveForm {...props} />
</Section.SectionBody>
</Section>
);
};
export default inject(({ auth }) => ({
greetingTitle: auth.settingsStore.greetingSettings,
theme: auth.settingsStore.theme,
logout: auth.logout,
}))(
withRouter(
withTranslation("Confirm")(withLoader(observer(ProfileRemoveFormWrapper)))
withTranslation("Confirm")(withLoader(observer(ProfileRemoveForm)))
)
);

View File

@ -2,7 +2,6 @@ import React, { useState } from "react";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import Section from "@docspace/common/components/Section";
import Text from "@docspace/components/text";
import Button from "@docspace/components/button";
import toastr from "@docspace/components/toast/toastr";
@ -10,7 +9,7 @@ import { deletePortal } from "@docspace/common/api/portal";
import {
StyledPage,
StyledBody,
StyledHeader,
StyledContent,
ButtonsWrapper,
} from "./StyledConfirm";
@ -36,56 +35,44 @@ const RemovePortal = (props) => {
return (
<StyledPage>
<StyledBody>
<StyledHeader>
<StyledContent>
<StyledBody>
<DocspaceLogo className="docspace-logo" />
<Text fontSize="23px" fontWeight="700" className="title">
{greetingTitle}
</Text>
</StyledHeader>
<FormWrapper>
<Text className="subtitle">{t("PortalRemoveTitle")}</Text>
<ButtonsWrapper>
<Button
primary
scale
size="medium"
label={t("Common:Delete")}
tabIndex={1}
onClick={onDeleteClick}
/>
<Button
scale
size="medium"
label={t("Common:CancelButton")}
tabIndex={1}
onClick={onCancelClick}
/>
</ButtonsWrapper>
</FormWrapper>
</StyledBody>
<FormWrapper>
<Text className="subtitle">{t("PortalRemoveTitle")}</Text>
<ButtonsWrapper>
<Button
primary
scale
size="medium"
label={t("Common:Delete")}
tabIndex={1}
onClick={onDeleteClick}
/>
<Button
scale
size="medium"
label={t("Common:CancelButton")}
tabIndex={1}
onClick={onCancelClick}
/>
</ButtonsWrapper>
</FormWrapper>
</StyledBody>
</StyledContent>
</StyledPage>
);
};
const RemovePortalWrapper = (props) => {
return (
<Section>
<Section.SectionBody>
<RemovePortal {...props} />
</Section.SectionBody>
</Section>
);
};
export default inject(({ auth }) => ({
greetingTitle: auth.settingsStore.greetingSettings,
theme: auth.settingsStore.theme,
}))(
withRouter(
withTranslation(["Confirm", "Common"])(
withLoader(observer(RemovePortalWrapper))
)
withTranslation(["Confirm", "Common"])(withLoader(observer(RemovePortal)))
)
);

View File

@ -6,7 +6,6 @@ import Button from "@docspace/components/button";
import TextInput from "@docspace/components/text-input";
import FieldContainer from "@docspace/components/field-container";
import Text from "@docspace/components/text";
import Section from "@docspace/common/components/Section";
import { inject, observer } from "mobx-react";
import Box from "@docspace/components/box";
import withLoader from "../withLoader";
@ -16,9 +15,10 @@ import { hugeMobile, tablet } from "@docspace/components/utils/device";
import Link from "@docspace/components/link";
import FormWrapper from "@docspace/components/form-wrapper";
import DocspaceLogo from "../../../DocspaceLogo";
import { StyledPage, StyledContent } from "./StyledConfirm";
const StyledForm = styled(Box)`
margin-top: 63px;
margin: 56px auto;
display: flex;
flex: 1fr 1fr;
gap: 80px;
@ -26,18 +26,14 @@ const StyledForm = styled(Box)`
justify-content: center;
@media ${tablet} {
margin: 100px auto 0 auto;
display: flex;
flex: 1fr;
flex-direction: column;
align-items: center;
gap: 32px;
}
@media ${hugeMobile} {
margin-top: 32px;
width: 100%;
flex: 1fr;
margin: 0 auto;
flex-direction: column;
gap: 0px;
padding-right: 8px;
@ -52,7 +48,7 @@ const StyledForm = styled(Box)`
}
.docspace-logo {
padding-bottom: 64px;
padding-bottom: 40px;
@media ${tablet} {
display: flex;
@ -142,97 +138,109 @@ const TfaActivationForm = withLoader((props) => {
};
return (
<StyledForm className="set-app-container">
<Box className="set-app-description" marginProp="0 0 32px 0">
<DocspaceLogo className="docspace-logo" />
<Text isBold fontSize="14px" className="set-app-title">
{t("SetAppTitle")}
</Text>
<StyledPage>
<StyledContent>
<StyledForm className="set-app-container">
<Box className="set-app-description" marginProp="0 0 32px 0">
<DocspaceLogo className="docspace-logo" />
<Text isBold fontSize="14px" className="set-app-title">
{t("SetAppTitle")}
</Text>
<Trans t={t} i18nKey="SetAppDescription" ns="Confirm">
The two-factor authentication is enabled to provide additional portal
security. Configure your authenticator application to continue work on
the portal. For example you could use Google Authenticator for
<Link isHovered href={props.tfaAndroidAppUrl} target="_blank">
Android
</Link>
and{" "}
<Link isHovered href={props.tfaIosAppUrl} target="_blank">
iOS
</Link>{" "}
or Authenticator for{" "}
<Link isHovered href={props.tfaWinAppUrl} target="_blank">
Windows Phone
</Link>{" "}
.
</Trans>
<Trans t={t} i18nKey="SetAppDescription" ns="Confirm">
The two-factor authentication is enabled to provide additional
portal security. Configure your authenticator application to
continue work on the portal. For example you could use Google
Authenticator for
<Link isHovered href={props.tfaAndroidAppUrl} target="_blank">
Android
</Link>
and{" "}
<Link isHovered href={props.tfaIosAppUrl} target="_blank">
iOS
</Link>{" "}
or Authenticator for{" "}
<Link isHovered href={props.tfaWinAppUrl} target="_blank">
Windows Phone
</Link>{" "}
.
</Trans>
<Text className="set-app-text">
<Trans
t={t}
i18nKey="SetAppInstallDescription"
ns="Confirm"
key={secretKey}
>
To connect your apllication scan the QR code or manually enter your
secret key <strong>{{ secretKey }}</strong> then enter 6-digit code
from your application in the field below.
</Trans>
</Text>
</Box>
<FormWrapper>
<Box
displayProp="flex"
flexDirection="column"
className="app-code-wrapper"
>
<div className="qrcode-wrapper">
<img src={qrCode} height="180px" width="180px" alt="QR-code"></img>
</div>
<Box className="app-code-input">
<FieldContainer
labelVisible={false}
hasError={error ? true : false}
errorMessage={error}
<Text className="set-app-text">
<Trans
t={t}
i18nKey="SetAppInstallDescription"
ns="Confirm"
key={secretKey}
>
To connect your apllication scan the QR code or manually enter
your secret key <strong>{{ secretKey }}</strong> then enter
6-digit code from your application in the field below.
</Trans>
</Text>
</Box>
<FormWrapper>
<Box
displayProp="flex"
flexDirection="column"
className="app-code-wrapper"
>
<TextInput
id="code"
name="code"
type="text"
size="large"
scale
isAutoFocussed
tabIndex={1}
placeholder={t("EnterCodePlaceholder")}
isDisabled={isLoading}
maxLength={6}
onChange={(e) => {
setCode(e.target.value);
setError("");
}}
value={code}
hasError={error ? true : false}
onKeyDown={onKeyPress}
/>
</FieldContainer>
</Box>
<Box className="app-code-continue-btn">
<Button
scale
primary
size="medium"
tabIndex={3}
label={
isLoading ? t("Common:LoadingProcessing") : t("SetAppButton")
}
isDisabled={!code.length || isLoading}
isLoading={isLoading}
onClick={onSubmit}
/>
</Box>
</Box>
</FormWrapper>
</StyledForm>
<div className="qrcode-wrapper">
<img
src={qrCode}
height="180px"
width="180px"
alt="QR-code"
></img>
</div>
<Box className="app-code-input">
<FieldContainer
labelVisible={false}
hasError={error ? true : false}
errorMessage={error}
>
<TextInput
id="code"
name="code"
type="text"
size="large"
scale
isAutoFocussed
tabIndex={1}
placeholder={t("EnterCodePlaceholder")}
isDisabled={isLoading}
maxLength={6}
onChange={(e) => {
setCode(e.target.value);
setError("");
}}
value={code}
hasError={error ? true : false}
onKeyDown={onKeyPress}
/>
</FieldContainer>
</Box>
<Box className="app-code-continue-btn">
<Button
scale
primary
size="medium"
tabIndex={3}
label={
isLoading
? t("Common:LoadingProcessing")
: t("SetAppButton")
}
isDisabled={!code.length || isLoading}
isLoading={isLoading}
onClick={onSubmit}
/>
</Box>
</Box>
</FormWrapper>
</StyledForm>
</StyledContent>
</StyledPage>
);
});
@ -264,11 +272,7 @@ const TfaActivationWrapper = (props) => {
return error ? (
<ErrorContainer bodyText={error} />
) : (
<Section>
<Section.SectionBody>
<TfaActivationForm secretKey={secretKey} qrCode={qrCode} {...props} />
</Section.SectionBody>
</Section>
<TfaActivationForm secretKey={secretKey} qrCode={qrCode} {...props} />
);
};

View File

@ -6,46 +6,32 @@ import Button from "@docspace/components/button";
import TextInput from "@docspace/components/text-input";
import FieldContainer from "@docspace/components/field-container";
import Text from "@docspace/components/text";
import Section from "@docspace/common/components/Section";
import { inject, observer } from "mobx-react";
import Box from "@docspace/components/box";
import toastr from "@docspace/components/toast/toastr";
import withLoader from "../withLoader";
import {
hugeMobile,
smallTablet,
tablet,
} from "@docspace/components/utils/device";
import { hugeMobile } from "@docspace/components/utils/device";
import FormWrapper from "@docspace/components/form-wrapper";
import DocspaceLogo from "../../../DocspaceLogo";
import { StyledPage, StyledContent } from "./StyledConfirm";
const StyledForm = styled(Box)`
margin: 63px auto;
width: 320px;
margin: 56px auto;
display: flex;
flex-direction: column;
flex: 1fr;
@media ${tablet} {
margin: 120px auto;
width: 480px;
}
@media ${smallTablet} {
width: 400px;
}
@media ${hugeMobile} {
margin: 32px 8px auto 8px;
padding-left: 8px;
margin: 0 auto;
width: 100%;
}
.docspace-logo {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 64px;
padding-bottom: 40px;
}
.app-code-wrapper {
@ -94,66 +80,70 @@ const TfaAuthForm = withLoader((props) => {
};
return (
<StyledForm className="app-code-container">
<DocspaceLogo className="docspace-logo" />
<FormWrapper>
<Box className="app-code-description" marginProp="0 0 32px 0">
<Text isBold fontSize="14px" className="app-code-text">
{t("EnterAppCodeTitle")}
</Text>
<Text>{t("EnterAppCodeDescription")}</Text>
</Box>
<Box
displayProp="flex"
flexDirection="column"
className="app-code-wrapper"
>
<Box className="app-code-input">
<FieldContainer
labelVisible={false}
hasError={error ? true : false}
errorMessage={error}
<StyledPage>
<StyledContent>
<StyledForm className="app-code-container">
<DocspaceLogo className="docspace-logo" />
<FormWrapper>
<Box className="app-code-description" marginProp="0 0 32px 0">
<Text isBold fontSize="14px" className="app-code-text">
{t("EnterAppCodeTitle")}
</Text>
<Text>{t("EnterAppCodeDescription")}</Text>
</Box>
<Box
displayProp="flex"
flexDirection="column"
className="app-code-wrapper"
>
<TextInput
id="code"
name="code"
type="text"
size="huge"
scale
isAutoFocussed
tabIndex={1}
placeholder={t("EnterCodePlaceholder")}
isDisabled={isLoading}
maxLength={6}
onChange={(e) => {
setCode(e.target.value);
setError("");
}}
value={code}
hasError={error ? true : false}
onKeyDown={onKeyPress}
/>
</FieldContainer>
</Box>
<Box className="app-code-continue-btn">
<Button
scale
primary
size="medium"
tabIndex={3}
label={
isLoading
? t("Common:LoadingProcessing")
: t("Common:ContinueButton")
}
isDisabled={!code.length || isLoading}
isLoading={isLoading}
onClick={onSubmit}
/>
</Box>
</Box>
</FormWrapper>
</StyledForm>
<Box className="app-code-input">
<FieldContainer
labelVisible={false}
hasError={error ? true : false}
errorMessage={error}
>
<TextInput
id="code"
name="code"
type="text"
size="huge"
scale
isAutoFocussed
tabIndex={1}
placeholder={t("EnterCodePlaceholder")}
isDisabled={isLoading}
maxLength={6}
onChange={(e) => {
setCode(e.target.value);
setError("");
}}
value={code}
hasError={error ? true : false}
onKeyDown={onKeyPress}
/>
</FieldContainer>
</Box>
<Box className="app-code-continue-btn">
<Button
scale
primary
size="medium"
tabIndex={3}
label={
isLoading
? t("Common:LoadingProcessing")
: t("Common:ContinueButton")
}
isDisabled={!code.length || isLoading}
isLoading={isLoading}
onClick={onSubmit}
/>
</Box>
</Box>
</FormWrapper>
</StyledForm>
</StyledContent>
</StyledPage>
);
});
@ -165,13 +155,7 @@ const TfaAuthFormWrapper = (props) => {
setIsLoading(false);
}, []);
return (
<Section>
<Section.SectionBody>
<TfaAuthForm {...props} />
</Section.SectionBody>
</Section>
);
return <TfaAuthForm {...props} />;
};
export default inject(({ auth, confirm }) => ({

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

@ -11,6 +11,7 @@ const FilesMediaViewer = (props) => {
t,
files,
playlist,
currentPostionIndex,
visible,
currentMediaFileId,
deleteItemAction,
@ -26,6 +27,7 @@ const FilesMediaViewer = (props) => {
setToPreviewFile,
setScrollToItem,
setCurrentId,
setAlreadyFetchingRooms,
setBufferSelection,
isFavoritesFolder,
archiveRoomsId,
@ -43,10 +45,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;
@ -62,6 +73,7 @@ const FilesMediaViewer = (props) => {
fetchFiles(previewFile.folderId).finally(() => {
setIsLoading(false);
setFirstLoad(false);
setAlreadyFetchingRooms(false);
});
}
}, [previewFile]);
@ -88,6 +100,10 @@ const FilesMediaViewer = (props) => {
window.history.pushState(null, null, url);
};
const resetSelection = () => {
setSelection([]);
};
const removeQuery = (queryName) => {
const queryParams = new URLSearchParams(location.search);
@ -108,9 +124,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 +151,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 +179,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 +200,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 +231,20 @@ export default inject(
setIsPreview,
isPreview,
resetUrl,
setSelection,
setAlreadyFetchingRooms,
} = filesStore;
const {
visible,
id: currentMediaFileId,
currentPostionIndex,
setMediaViewerData,
playlist,
previewFile,
setToPreviewFile,
setCurrentId,
nextMedia,
prevMedia,
} = mediaViewerDataStore;
const { deleteItemAction } = filesActionsStore;
const { getIcon, extsImagePreviewed, extsMediaPreviewed } = settingsStore;
@ -262,6 +265,9 @@ export default inject(
return {
files,
playlist,
currentPostionIndex,
nextMedia,
prevMedia,
userAccess,
visible: playlist.length > 0 && visible,
currentMediaFileId,
@ -283,6 +289,7 @@ export default inject(
setScrollToItem,
setCurrentId,
setBufferSelection,
setAlreadyFetchingRooms,
isFavoritesFolder,
onClickFavorite,
onClickDownloadAs,
@ -295,6 +302,7 @@ export default inject(
onCopyAction,
onDuplicate,
archiveRoomsId,
setSelection,
};
}
)(

View File

@ -81,6 +81,7 @@ const FilesRowContainer = ({
fetchMoreFiles,
hasMoreFiles,
isRooms,
isTrashFolder,
withPaging,
setUploadedFileIdWithVersion,
}) => {
@ -118,6 +119,7 @@ const FilesRowContainer = ({
itemIndex={index}
sectionWidth={sectionWidth}
isRooms={isRooms}
isTrashFolder={isTrashFolder}
setUploadedFileIdWithVersion={setUploadedFileIdWithVersion}
/>
))}
@ -137,7 +139,7 @@ export default inject(({ filesStore, auth, treeFoldersStore }) => {
setUploadedFileIdWithVersion,
} = filesStore;
const { isVisible: infoPanelVisible } = auth.infoPanelStore;
const { isRoomsFolder, isArchiveFolder } = treeFoldersStore;
const { isRoomsFolder, isArchiveFolder, isTrashFolder } = treeFoldersStore;
const { withPaging } = auth.settingsStore;
const isRooms = isRoomsFolder || isArchiveFolder;
@ -151,6 +153,7 @@ export default inject(({ filesStore, auth, treeFoldersStore }) => {
fetchMoreFiles,
hasMoreFiles,
isRooms,
isTrashFolder,
withPaging,
setUploadedFileIdWithVersion,
};

View File

@ -4,6 +4,7 @@ import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import styled from "styled-components";
import { isMobile, isTablet, isMobileOnly } from "react-device-detect";
import moment from "moment";
import Link from "@docspace/components/link";
import Text from "@docspace/components/text";
@ -94,15 +95,18 @@ const FilesRowContent = ({
quickButtons,
theme,
isRooms,
isTrashFolder,
}) => {
const {
contentLength,
fileExst,
filesCount,
foldersCount,
autoDelete,
providerKey,
title,
isRoom,
daysRemaining,
} = item;
return (
@ -138,7 +142,11 @@ const FilesRowContent = ({
// color={sideColor}
className="row_update-text"
>
{updatedDate && updatedDate}
{isTrashFolder
? t("Files:DaysRemaining", {
daysRemaining,
})
: updatedDate && updatedDate}
</Text>
<Text
@ -166,8 +174,9 @@ const FilesRowContent = ({
);
};
export default inject(({ auth }) => {
return { theme: auth.settingsStore.theme };
export default inject(({ auth, treeFoldersStore }) => {
const { isRecycleBinFolder } = treeFoldersStore;
return { theme: auth.settingsStore.theme, isTrashFolder: isRecycleBinFolder };
})(
observer(
withRouter(

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,7 +14,7 @@ class FilesTableHeader extends React.Component {
}
getTableColumns = (fromUpdate = false) => {
const { t, isRooms, getColumns } = this.props;
const { t, isRooms, isTrashFolder, getColumns } = this.props;
const defaultColumns = [];
@ -68,7 +68,81 @@ 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: "Room",
title: t("Common:Room"),
enable: this.props.roomColumnIsEnabled,
resizable: true,
sortBy: "Room",
onClick: this.onFilter,
onChange: this.onColumnChange,
},
{
key: "AuthorTrash",
title: t("ByAuthor"),
enable: this.props.authorTrashColumnIsEnabled,
resizable: true,
sortBy: "Author",
onClick: this.onFilter,
onChange: this.onColumnChange,
},
{
key: "CreatedTrash",
title: t("ByCreation"),
enable: this.props.createdTrashColumnIsEnabled,
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: "SizeTrash",
title: t("Common:Size"),
enable: this.props.sizeTrashColumnIsEnabled,
resizable: true,
sortBy: "Size",
onClick: this.onFilter,
onChange: this.onColumnChange,
},
{
key: "TypeTrash",
title: t("Common:Type"),
enable: this.props.typeTrashColumnIsEnabled,
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 +209,6 @@ class FilesTableHeader extends React.Component {
resizable: false,
},
];
defaultColumns.push(...columns);
}
@ -194,7 +267,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 +428,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;
@ -369,10 +450,16 @@ export default inject(
nameColumnIsEnabled,
authorColumnIsEnabled,
authorTrashColumnIsEnabled,
createdColumnIsEnabled,
createdTrashColumnIsEnabled,
modifiedColumnIsEnabled,
roomColumnIsEnabled,
erasureColumnIsEnabled,
sizeColumnIsEnabled,
sizeTrashColumnIsEnabled,
typeColumnIsEnabled,
typeTrashColumnIsEnabled,
quickButtonsColumnIsEnabled,
roomColumnNameIsEnabled,
@ -414,10 +501,16 @@ export default inject(
nameColumnIsEnabled,
authorColumnIsEnabled,
authorTrashColumnIsEnabled,
createdColumnIsEnabled,
createdTrashColumnIsEnabled,
modifiedColumnIsEnabled,
roomColumnIsEnabled,
erasureColumnIsEnabled,
sizeColumnIsEnabled,
sizeTrashColumnIsEnabled,
typeColumnIsEnabled,
typeTrashColumnIsEnabled,
quickButtonsColumnIsEnabled,
roomColumnNameIsEnabled,
@ -429,6 +522,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,22 @@
import React from "react";
import { StyledText } from "./CellStyles";
const ErasureCell = ({ t, sideColor, item }) => {
const { daysRemaining } = item;
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

@ -0,0 +1,66 @@
import { Loader, Tooltip } from "@docspace/components";
import Text from "@docspace/components/text";
import React, { useState } from "react";
import { StyledText } from "./CellStyles";
import { getFolderPath } from "@docspace/common/api/files";
import { CategoryType } from "@docspace/client/src/helpers/constants";
const RoomCell = ({ sideColor, item }) => {
const { originRoomTitle, originId, originTitle } = item;
const [path, setPath] = useState([]);
const [isTooltipLoading, setIsTooltipLoading] = useState(false);
const getPath = async () => {
if (path.length) return;
setIsTooltipLoading(true);
try {
const folderPath = await getFolderPath(originId);
if (folderPath[0].id === CategoryType.Shared) folderPath.shift();
setPath(folderPath);
} catch (e) {
console.error(e);
setPath([{ id: 0, title: originRoomTitle || originTitle }]);
}
setIsTooltipLoading(false);
};
return [
<StyledText
key="cell"
fontSize="12px"
fontWeight={600}
color={sideColor}
className="row_update-text"
truncate
data-for={"" + item.id}
data-tip={""}
data-place={"bottom"}
>
{originRoomTitle || originTitle}
</StyledText>,
<Tooltip
id={"" + item.id}
key={"tooltip"}
effect={"float"}
afterShow={getPath}
getContent={() => (
<span>
{isTooltipLoading ? (
<Loader color="#333333" size="12px" type="track" />
) : (
path.map((pathPart, i) => (
<Text key={pathPart.id} isBold={i === 0} isInline fontSize="12px">
{i === 0 ? pathPart.title : `/${pathPart.title}`}
</Text>
))
)}
</span>
)}
/>,
];
};
export default RoomCell;

View File

@ -0,0 +1,211 @@
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";
import RoomCell from "./RoomCell";
const TrashRowDataComponent = (props) => {
const {
authorTrashColumnIsEnabled,
createdTrashColumnIsEnabled,
roomColumnIsEnabled,
erasureColumnIsEnabled,
sizeTrashColumnIsEnabled,
typeTrashColumnIsEnabled,
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>
{roomColumnIsEnabled ? (
<TableCell
style={
!roomColumnIsEnabled ? { background: "none" } : dragStyles.style
}
{...selectionProp}
>
<RoomCell
sideColor={theme.filesSection.tableView.row.sideColor}
{...props}
/>
</TableCell>
) : (
<div />
)}
{authorTrashColumnIsEnabled ? (
<TableCell
style={
!authorTrashColumnIsEnabled
? { background: "none" }
: dragStyles.style
}
{...selectionProp}
>
<AuthorCell
sideColor={theme.filesSection.tableView.row.sideColor}
{...props}
/>
</TableCell>
) : (
<div />
)}
{createdTrashColumnIsEnabled ? (
<TableCell
style={
!createdTrashColumnIsEnabled
? { 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 />
)}
{sizeTrashColumnIsEnabled ? (
<TableCell
style={
!sizeTrashColumnIsEnabled
? { background: "none" }
: dragStyles.style
}
{...selectionProp}
>
<SizeCell
sideColor={theme.filesSection.tableView.row.sideColor}
{...props}
/>
</TableCell>
) : (
<div />
)}
{typeTrashColumnIsEnabled ? (
<TableCell
style={
!typeTrashColumnIsEnabled
? { 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 {
authorTrashColumnIsEnabled,
createdTrashColumnIsEnabled,
roomColumnIsEnabled,
erasureColumnIsEnabled,
sizeTrashColumnIsEnabled,
typeTrashColumnIsEnabled,
quickButtonsColumnIsEnabled,
} = tableStore;
return {
authorTrashColumnIsEnabled,
createdTrashColumnIsEnabled,
roomColumnIsEnabled,
erasureColumnIsEnabled,
sizeTrashColumnIsEnabled,
typeTrashColumnIsEnabled,
quickButtonsColumnIsEnabled,
};
})(observer(TrashRowDataComponent));

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,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(() => {
@ -1046,22 +1053,10 @@ const SectionFilterContent = ({
label: t("ByLastModified"),
default: true,
};
const type = {
id: "sort-by_type",
key: "Type",
label: t("Common:Type"),
default: true,
};
const size = {
id: "sort-by_size",
key: "Size",
label: t("Common:Size"),
default: true,
};
const creationDate = {
id: "sort-by_created",
key: "DateAndTimeCreation",
label: t("ByCreation"),
const room = {
id: "sort-by_room",
key: "Room",
label: t("Common:Room"),
default: true,
};
const authorOption = {
@ -1070,18 +1065,42 @@ const SectionFilterContent = ({
label: t("ByAuthor"),
default: true,
};
const creationDate = {
id: "sort-by_created",
key: "DateAndTimeCreation",
label: t("ByCreation"),
default: true,
};
const owner = {
id: "sort-by_owner",
key: "Author",
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",
label: t("Common:Tags"),
default: true,
};
const size = {
id: "sort-by_size",
key: "Size",
label: t("Common:Size"),
default: true,
};
const type = {
id: "sort-by_type",
key: "Type",
label: t("Common:Type"),
default: true,
};
const roomType = {
id: "sort-by_room-type",
key: "roomType",
@ -1140,6 +1159,69 @@ 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("Room")) {
const idx = availableSort.findIndex((x) => x === "Room");
const hide =
infoPanelVisible &&
infoPanelColumnsSize &&
infoPanelColumnsSize[idx] === "0px";
!hide && commonOptions.push(room);
}
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 +1283,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 +1391,12 @@ const SectionFilterContent = ({
]
);
const onSortButtonClick = (isOpen) => {
if (isMobileOnly) {
setMainButtonMobileVisible(isOpen);
}
};
const clearAll = () => {
if (isRooms) {
setIsLoading(true);
@ -1346,6 +1440,7 @@ const SectionFilterContent = ({
filterTitle={t("Filter")}
clearSearch={clearSearch}
setClearSearch={setClearSearch}
onSortButtonClick={onSortButtonClick}
/>
);
};
@ -1369,6 +1464,7 @@ export default inject(
viewAs,
createThumbnails,
setCurrentRoomsFilter,
setMainButtonMobileVisible,
thirdPartyStore,
clearSearch,
setClearSearch,
@ -1388,6 +1484,7 @@ export default inject(
isRoomsFolder,
isArchiveFolder,
isPersonalRoom,
isTrashFolder: isTrash,
} = treeFoldersStore;
const isRooms = isRoomsFolder || isArchiveFolder;
@ -1413,6 +1510,7 @@ export default inject(
isFavoritesFolder,
isRecentFolder,
isRooms,
isTrash,
setIsLoading,
fetchFiles,
@ -1437,6 +1535,8 @@ export default inject(
clearSearch,
setClearSearch,
setMainButtonMobileVisible,
};
}
)(

View File

@ -36,6 +36,7 @@ import { Consumer } from "@docspace/components/utils/context";
import { inject, observer } from "mobx-react";
import TableGroupMenu from "@docspace/components/table-container/TableGroupMenu";
import Navigation from "@docspace/common/components/Navigation";
import TrashWarning from "@docspace/common/components/Navigation/sub-components/trash-warning";
import { Events } from "@docspace/common/constants";
import config from "PACKAGE_FILE";
import { combineUrl } from "@docspace/common/utils";
@ -685,11 +686,15 @@ class SectionHeaderContent extends React.Component {
const menuItems = this.getMenuItems();
const isLoading = !title || !tReady;
const headerMenu = getHeaderMenu(t);
const isEmptyTrash = !![
...this.props.activeFiles,
...this.props.activeFolders,
].length;
return (
<Consumer>
return [
<Consumer key="header">
{(context) => (
<StyledContainer>
<StyledContainer isRecycleBinFolder={isRecycleBinFolder}>
{isHeaderVisible && headerMenu.length ? (
<TableGroupMenu
checkboxOptions={menuItems}
@ -727,6 +732,7 @@ class SectionHeaderContent extends React.Component {
getContextOptionsFolder={this.getContextOptionsFolder}
onClose={this.onClose}
onClickFolder={this.onClickFolder}
isTrashFolder={isRecycleBinFolder}
isRecycleBinFolder={isRecycleBinFolder || isArchiveFolder}
isEmptyFilesList={
isArchiveFolder ? isEmptyArchive : isEmptyFilesList
@ -737,6 +743,7 @@ class SectionHeaderContent extends React.Component {
isInfoPanelVisible={isInfoPanelVisible}
titles={{
trash: t("EmptyRecycleBin"),
trashWarning: t("TrashErasureWarning"),
}}
withMenu={!isRoomsFolder}
onPlusClick={this.onCreateRoom}
@ -748,8 +755,15 @@ class SectionHeaderContent extends React.Component {
)}
</StyledContainer>
)}
</Consumer>
);
</Consumer>,
isRecycleBinFolder && !isEmptyPage && (
<TrashWarning
key="trash-warning"
title={t("Files:TrashErasureWarning")}
isTabletView
/>
),
];
}
}

View File

@ -528,6 +528,7 @@ class PureHome extends React.Component {
isHeaderVisible={isHeaderVisible}
onOpenUploadPanel={this.showUploadPanel}
firstLoad={firstLoad}
isEmptyPage={isEmptyPage}
>
{!isErrorRoomNotAvailable && (
<Section.SectionHeader>

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

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

@ -30,6 +30,8 @@ const PersonalSettings = ({
showTitle,
createWithoutDialog,
setCreateWithoutDialog,
showAdminSettings,
}) => {
const [isLoadingFavorites, setIsLoadingFavorites] = React.useState(false);
const [isLoadingRecent, setIsLoadingRecent] = React.useState(false);
@ -75,7 +77,10 @@ const PersonalSettings = ({
};
return (
<StyledSettings showTitle={showTitle}>
<StyledSettings
showTitle={showTitle}
hideAdminSettings={!showAdminSettings}
>
<Box className="settings-section">
{showTitle && (
<Heading className="heading" level={2} size="xsmall">

View File

@ -3,10 +3,22 @@ import { tablet } from "@docspace/components/utils/device";
import { isMobile } from "react-device-detect";
const StyledSettings = styled.div`
margin-top: ${(props) => (props.showTitle ? 24 : 34)}px;
margin-top: ${(props) =>
props.hideAdminSettings ? 22 : props.showTitle ? 24 : 34}px;
${(props) =>
props.hideAdminSettings &&
css`
padding-top: 2px;
`}
@media ${tablet} {
margin-top: 8px;
margin-top: ${(props) => (props.hideAdminSettings ? 0 : 8)}px;
${(props) =>
props.hideAdminSettings &&
css`
padding-top: 8px;
`}
}
${isMobile &&

View File

@ -70,7 +70,11 @@ const SectionBodyContent = ({ isErrorSettings, history, user }) => {
) : (
<StyledContainer>
{!showAdminSettings ? (
<PersonalSettings t={t} showTitle={true} />
<PersonalSettings
t={t}
showTitle={true}
showAdminSettings={showAdminSettings}
/>
) : (
<Submenu
data={data}

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

@ -16,7 +16,10 @@ import { isMobile, isMobileOnly } from "react-device-detect";
import toastr from "@docspace/components/toast/toastr";
import config from "PACKAGE_FILE";
import { thumbnailStatuses } from "@docspace/client/src/helpers/filesConstants";
import { openDocEditor as openEditor } from "@docspace/client/src/helpers/filesUtils";
import {
getDaysRemaining,
openDocEditor as openEditor,
} from "@docspace/client/src/helpers/filesUtils";
import { getCategoryUrl } from "SRC_DIR/helpers/utils";
import {
getCategoryType,
@ -101,6 +104,7 @@ class FilesStore {
pageItemsLength = null;
isHidePagination = false;
trashIsEmpty = false;
mainButtonMobileVisible = true;
filesIsLoading = false;
isEmptyPage = false;
@ -2380,6 +2384,8 @@ class FilesStore {
const newItem = items.map((item) => {
const {
access,
autoDelete,
originTitle,
comment,
contentLength,
created,
@ -2394,6 +2400,10 @@ class FilesStore {
id,
logo,
locked,
originId,
originFolderId,
originRoomId,
originRoomTitle,
parentId,
pureContentLength,
rootFolderType,
@ -2437,7 +2447,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;
@ -2493,6 +2503,8 @@ class FilesStore {
return {
access,
daysRemaining: autoDelete && getDaysRemaining(autoDelete),
originTitle,
//checked,
comment,
contentLength,
@ -2534,6 +2546,10 @@ class FilesStore {
canEdit,
thumbnailUrl,
thumbnailStatus,
originId,
originFolderId,
originRoomId,
originRoomTitle,
previewUrl,
folderUrl,
href,
@ -3088,6 +3104,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

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

@ -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,12 +25,19 @@ class TableStore {
nameColumnIsEnabled = true; // always true
authorColumnIsEnabled = false;
roomColumnIsEnabled = true;
erasureColumnIsEnabled = true;
createdColumnIsEnabled = true;
modifiedColumnIsEnabled = true;
sizeColumnIsEnabled = true;
typeColumnIsEnabled = true;
quickButtonsColumnIsEnabled = true;
authorTrashColumnIsEnabled = true;
createdTrashColumnIsEnabled = false;
sizeTrashColumnIsEnabled = false;
typeTrashColumnIsEnabled = false;
constructor(authStore, treeFoldersStore) {
makeAutoObservable(this);
@ -54,28 +64,51 @@ class TableStore {
setAuthorColumn = (enable) => {
this.authorColumnIsEnabled = enable;
};
setCreatedColumn = (enable) => {
this.createdColumnIsEnabled = enable;
};
setModifiedColumn = (enable) => {
this.modifiedColumnIsEnabled = enable;
};
setRoomColumn = (enable) => {
this.roomColumnIsEnabled = enable;
};
setErasureColumn = (enable) => {
this.erasureColumnIsEnabled = enable;
};
setSizeColumn = (enable) => {
this.sizeColumnIsEnabled = enable;
};
setTypeColumn = (enable) => {
this.typeColumnIsEnabled = enable;
};
setQuickButtonsColumn = (enable) => {
this.quickButtonsColumnIsEnabled = enable;
};
setAuthorTrashColumn = (enable) => (this.authorTrashColumnIsEnabled = enable);
setCreatedTrashColumn = (enable) =>
(this.createdTrashColumnIsEnabled = enable);
setSizeTrashColumn = (enable) => (this.sizeTrashColumnIsEnabled = enable);
setTypeTrashColumn = (enable) => (this.typeTrashColumnIsEnabled = enable);
setColumnsEnable = () => {
const storageColumns = localStorage.getItem(this.tableStorageName);
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,51 +116,96 @@ class TableStore {
this.setRoomColumnTags(splitColumns.includes("Tags"));
this.setRoomColumnOwner(splitColumns.includes("Owner"));
this.setRoomColumnActivity(splitColumns.includes("Activity"));
} else {
this.setAuthorColumn(splitColumns.includes("Author"));
this.setCreatedColumn(splitColumns.includes("Created"));
this.setModifiedColumn(splitColumns.includes("Modified"));
this.setSizeColumn(splitColumns.includes("Size"));
this.setTypeColumn(splitColumns.includes("Type"));
this.setQuickButtonsColumn(splitColumns.includes("QuickButtons"));
return;
}
if (isTrashFolder) {
this.setRoomColumn(splitColumns.includes("Room"));
this.setAuthorTrashColumn(splitColumns.includes("AuthorTrash"));
this.setCreatedTrashColumn(splitColumns.includes("CreatedTrash"));
this.setErasureColumn(splitColumns.includes("Erasure"));
this.setSizeTrashColumn(splitColumns.includes("SizeTrash"));
this.setTypeTrashColumn(splitColumns.includes("TypeTrash"));
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"));
}
};
setColumnEnable = (key) => {
const { isRoomsFolder, isArchiveFolder } = this.treeFoldersStore;
const {
isRoomsFolder,
isArchiveFolder,
isTrashFolder,
} = this.treeFoldersStore;
const isRooms = isRoomsFolder || isArchiveFolder;
switch (key) {
case "Room":
this.setRoomColumn(!this.roomColumnIsEnabled);
return;
case "Author":
this.setAuthorColumn(!this.authorColumnIsEnabled);
return;
case "AuthorTrash":
this.setAuthorTrashColumn(!this.authorTrashColumnIsEnabled);
return;
case "Created":
this.setCreatedColumn(!this.createdColumnIsEnabled);
return;
case "CreatedTrash":
this.setCreatedTrashColumn(!this.createdTrashColumnIsEnabled);
return;
case "Modified":
this.setModifiedColumn(!this.modifiedColumnIsEnabled);
return;
case "Erasure":
this.setErasureColumn(!this.erasureColumnIsEnabled);
return;
case "Size":
this.setSizeColumn(!this.sizeColumnIsEnabled);
return;
case "SizeTrash":
this.setSizeTrashColumn(!this.sizeTrashColumnIsEnabled);
return;
case "Type":
isRooms
? this.setRoomColumnType(!this.roomColumnTypeIsEnabled)
: this.setTypeColumn(!this.typeColumnIsEnabled);
return;
case "TypeTrash":
this.setTypeTrashColumn(!this.typeTrashColumnIsEnabled);
return;
case "QuickButtons":
this.setQuickButtonsColumn(!this.quickButtonsColumnIsEnabled);
return;
case "Owner":
this.setRoomColumnOwner(!this.roomColumnOwnerIsEnabled);
return;
case "Tags":
this.setRoomColumnTags(!this.roomColumnTagsIsEnabled);
return;
case "Activity":
this.setRoomColumnActivity(!this.roomColumnActivityIsEnabled);
return;
default:
return;
}
@ -155,32 +233,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 +288,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 +301,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

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

@ -1,10 +1,10 @@
import { request } from "../client";
import axios from "axios";
export function getSettings() {
export function getSettings(withPassword = false) {
return request({
method: "get",
url: "/settings.json",
url: `/settings.json?withPassword=${withPassword}`,
});
}

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

@ -6,8 +6,9 @@ const LoginContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin: 56px auto 0 auto;
margin: 56px auto;
max-width: 960px;
z-index: 0;
.remember-wrapper {
max-width: 142px;
@ -236,7 +237,7 @@ const LoginContainer = styled.div`
justify-content: center;
width: 100%;
height: 46px;
padding-bottom: 64px;
padding-bottom: 40px;
svg {
path:last-child {

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) => string;
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,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

@ -0,0 +1,68 @@
import {
ContextMenuModel,
NullOrUndefined,
PlaylistType,
SeparatorType,
} 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;
};
export const isSeparator = (arg: ContextMenuModel): arg is SeparatorType => {
return arg?.isSeparator;
};

View File

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

View File

@ -0,0 +1,465 @@
import { isMobileOnly } from "react-device-detect";
import React, { useState, useCallback, useMemo, useEffect } from "react";
import ViewerWrapper from "./sub-components/ViewerWrapper";
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() : "";
}, []);
let lastRemovedFileId: null | number = null;
const onDelete = () => {
const { playlist, onDelete } = props;
let currentFileId = playlist.find((file) => file.id === playlistPos)
?.fileId;
if (currentFileId === lastRemovedFileId) return;
const canDelete = targetFile?.security?.Delete;
if (!canDelete) return;
if (!isNullOrUndefined(currentFileId)) {
onDelete(currentFileId);
lastRemovedFileId = 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 && (
<ViewerWrapper
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,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

@ -0,0 +1,107 @@
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;
}
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

@ -19,6 +19,7 @@ import {
isDesktop as isDesktopUtils,
} from "@docspace/components/utils/device";
import ToggleInfoPanelButton from "./sub-components/toggle-infopanel-btn";
import TrashWarning from "./sub-components/trash-warning";
const Navigation = ({
tReady,
@ -33,6 +34,7 @@ const Navigation = ({
getContextOptionsPlus,
getContextOptionsFolder,
onBackToParentFolder,
isTrashFolder,
isRecycleBinFolder,
isEmptyFilesList,
clearTrash,
@ -168,6 +170,7 @@ const Navigation = ({
isRootFolder={isRootFolder}
canCreate={canCreate}
isTabletView={isTabletView}
isTrashFolder={isTrashFolder}
isRecycleBinFolder={isRecycleBinFolder}
isDesktop={isDesktop}
isDesktopClient={isDesktopClient}
@ -200,6 +203,9 @@ const Navigation = ({
onPlusClick={onPlusClick}
/>
</StyledContainer>
{isTrashFolder && !isEmptyPage && (
<TrashWarning title={titles.trashWarning} />
)}
{infoPanelIsVisible && (
<ToggleInfoPanelButton
id="info-panel-toggle--open"

View File

@ -9,7 +9,7 @@ import {
const StyledContainer = styled.div`
${(props) =>
!props.isDropBox &&
!props.isDropBoxComponent &&
props.isDesktop &&
css`
width: fit-content;
@ -26,6 +26,7 @@ const StyledContainer = styled.div`
height: 100%;
${(props) =>
props.isDesktopClient &&
props.isDropBoxComponent &&
css`
max-height: 32px;
`}

View File

@ -90,7 +90,7 @@ StyledInfoPanelToggleWrapper.defaultProps = { theme: Base };
const ControlButtons = ({
personal,
isDropBox,
isDropBoxComponent,
isRootFolder,
canCreate,
getContextOptionsFolder,
@ -112,7 +112,7 @@ const ControlButtons = ({
};
return (
<StyledContainer isDropBox={isDropBox}>
<StyledContainer isDropBoxComponent={isDropBoxComponent}>
{(!isRootFolder && canCreate) ||
(isRecycleBinFolder && !isEmptyFilesList) ? (
<>

View File

@ -156,7 +156,7 @@ const DropBox = React.forwardRef(
>
<StyledContainer
canCreate={canCreate}
isDropBox={true}
isDropBoxComponent={true}
isInfoPanelVisible={isInfoPanelVisible}
isDesktopClient={isDesktopClient}
>
@ -169,7 +169,7 @@ const DropBox = React.forwardRef(
isDesktop={isDesktop}
personal={personal}
isRootFolder={isRootFolder}
isDropBox={true}
isDropBoxComponent={true}
canCreate={canCreate}
getContextOptionsFolder={getContextOptionsFolder}
getContextOptionsPlus={getContextOptionsPlus}

View File

@ -0,0 +1,47 @@
import React from "react";
import styled, { css } from "styled-components";
import { tablet } from "@docspace/components/utils/device";
const StyledTrashWarning = styled.div`
box-sizing: border-box;
height: 32px;
padding: 8px 12px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: left;
font-weight: 400;
font-size: 12px;
line-height: 16px;
color: ${({ theme }) => theme.section.header.trashErasureLabelText};
background: ${({ theme }) =>
theme.section.header.trashErasureLabelBackground};
${({ isTabletView }) =>
!isTabletView
? css`
@media ${tablet} {
display: none;
}
`
: css`
margin-bottom: 16px;
display: none;
@media ${tablet} {
display: flex;
}
`}
`;
const TrashWarning = ({ title, isTabletView }) => {
return (
<StyledTrashWarning className="trash-warning" isTabletView={isTabletView}>
{title}
</StyledTrashWarning>
);
};
export default TrashWarning;

View File

@ -225,6 +225,7 @@ class Section extends React.Component {
isHeaderVisible={isHeaderVisible}
viewAs={viewAs}
showText={showText}
isEmptyPage={this.props.isEmptyPage}
>
{sectionHeaderContent
? sectionHeaderContent.props.children
@ -261,6 +262,7 @@ class Section extends React.Component {
viewAs={viewAs}
showText={showText}
settingsStudio={settingsStudio}
isEmptyPage={this.props.isEmptyPage}
>
{sectionHeaderContent
? sectionHeaderContent.props.children
@ -387,6 +389,7 @@ Section.propTypes = {
isHeaderVisible: PropTypes.bool,
isInfoPanelAvailable: PropTypes.bool,
settingsStudio: PropTypes.bool,
isEmptyPage: PropTypes.bool,
};
Section.defaultProps = {

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

@ -41,6 +41,16 @@ const StyledSectionHeader = styled.div`
min-height: 53px;
`}
${({ isTrashFolder, isEmptyPage }) =>
isTrashFolder &&
!isEmptyPage &&
css`
@media ${tablet} {
height: 109px;
min-height: 109px;
}
`}
padding-right: 20px;
box-sizing: border-box;
@ -87,13 +97,24 @@ const StyledSectionHeader = styled.div`
StyledSectionHeader.defaultProps = { theme: Base };
const SectionHeader = (props) => {
const { viewAs, settingsStudio = false, className, ...rest } = props;
const {
viewAs,
settingsStudio = false,
className,
isEmptyPage,
...rest
} = props;
const pathname = window.location.pathname.toLowerCase();
const isTrashFolder = pathname.indexOf("trash") !== -1;
return (
<StyledSectionHeader
className={`section-header ${className}`}
isEmptyPage={isEmptyPage}
viewAs={viewAs}
settingsStudio={settingsStudio}
isTrashFolder={isTrashFolder}
{...rest}
/>
);

View File

@ -138,6 +138,7 @@ class SettingsStore {
companyInfoSettingsIsDefault = true;
whiteLabelLogoUrls = [];
standalone = false;
constructor() {
makeAutoObservable(this);
@ -187,12 +188,12 @@ class SettingsStore {
this.greetingSettings = greetingSettings;
};
getSettings = async () => {
getSettings = async (withPassword) => {
let newSettings = null;
if (window?.__ASC_INITIAL_EDITOR_STATE__?.portalSettings)
newSettings = window.__ASC_INITIAL_EDITOR_STATE__.portalSettings;
else newSettings = await api.settings.getSettings();
else newSettings = await api.settings.getSettings(withPassword);
if (window["AscDesktopEditor"] !== undefined || this.personal) {
const dp = combineUrl(

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,20 @@ class SelectionArea extends React.Component {
folderHeaderHeight,
} = this.props;
if (e.target.closest(".not-selectable")) return;
if (
e.target.closest(".not-selectable") ||
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

@ -1991,6 +1991,8 @@ const Base = {
header: {
backgroundColor: white,
background: `linear-gradient(180deg,#ffffff 2.81%,rgba(255, 255, 255, 0.91) 63.03%,rgba(255, 255, 255, 0) 100%)`,
trashErasureLabelBackground: "#f8f9f9",
trashErasureLabelText: "#555f65",
},
},

View File

@ -1982,6 +1982,8 @@ const Dark = {
header: {
backgroundColor: black,
background: `linear-gradient(180deg, #333333 2.81%, rgba(51, 51, 51, 0.9) 63.03%, rgba(51, 51, 51, 0) 100%);`,
trashErasureLabelBackground: "#292929",
trashErasureLabelText: "#ADADAD",
},
},

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

Some files were not shown because too many files have changed in this diff Show More