Merge branch 'hotfix/v2.6.1' into feature/api-separation

This commit is contained in:
Alexey Safronov 2024-08-08 14:38:16 +04:00
commit ee3ff7ed09
45 changed files with 662 additions and 250 deletions

View File

@ -12,6 +12,7 @@
"FeedLocationLabel": "Folder «{{folderTitle}}»",
"FeedLocationLabelFrom": "from «{{folderTitle}}»",
"FeedLocationRoomLabel": "Room «{{folderTitle}}»",
"FeedLocationSectionLabel": "Section «{{folderTitle}}»",
"FileConverted": "File converted.",
"FileCopied": "Files copied.",
"FileCopiedTo": "Files copied to «{{folderTitle}}»",

View File

@ -43,6 +43,7 @@ import { DeviceType, IndexedDBStores } from "@docspace/shared/enums";
import indexedDbHelper from "@docspace/shared/utils/indexedDBHelper";
import { useThemeDetector } from "@docspace/shared/hooks/useThemeDetector";
import { sendToastReport } from "@docspace/shared/utils/crashReport";
import { combineUrl } from "@docspace/shared/utils/combineUrl";
import config from "PACKAGE_FILE";
@ -77,6 +78,7 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
userTheme,
//user,
userId,
userLoginEventId,
currentDeviceType,
timezone,
showArticleLoader,
@ -134,6 +136,7 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
command: "subscribe",
data: { roomParts: "backup-restore" },
});
socketHelper.on("restore-backup", () => {
getRestoreProgress()
.then((response) => {
@ -159,7 +162,27 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
command: "subscribe",
data: { roomParts: "QUOTA", individual: true },
});
}, [socketHelper]);
socketHelper.emit({
command: "subscribe",
data: { roomParts: userId },
});
socketHelper.on("s:logout-session", (loginEventId) => {
console.log(`[WS] "logout-session"`, loginEventId, userLoginEventId);
if (userLoginEventId === loginEventId || loginEventId === 0) {
window.location.replace(
combineUrl(window.ClientConfig?.proxy?.url, "/login"),
);
}
});
}, [
socketHelper,
userLoginEventId,
setPreparationPortalDialogVisible,
userId,
]);
const { t, ready } = useTranslation(["Common"]); //TODO: if enable banner ["Common", "SmartBanner"]
@ -533,6 +556,7 @@ const ShellWrapper = inject(
setSnackbarExist,
userTheme: isFrame ? frameConfig?.theme : userTheme,
userId: userStore?.user?.id,
userLoginEventId: userStore?.user?.loginEventId,
currentDeviceType,
showArticleLoader: clientLoadingStore.showArticleLoader,
setPortalTariff,

View File

@ -254,7 +254,7 @@ const Badges = ({
</BadgeWrapper>
)}
{isEditing && !isVisitor && !(isRecentTab && !canEditing) && (
{isEditing && !(isRecentTab && !canEditing) && (
<ColorTheme
themeId={ThemeId.IconButton}
isEditing={isEditing}

View File

@ -75,19 +75,16 @@ export const getDescription = (
if (isFolder) {
return match([parentRoomType, folderType, access])
.with([P._, FolderType.Done, P._], () =>
t("Files:EmptyFormFolderDoneHeaderText"),
t("Files:EmptyFormFolderDoneDescriptionText"),
)
.with([P._, FolderType.InProgress, P._], () =>
t("Files:EmptyFormFolderProgressHeaderText"),
t("Files:EmptyFormFolderProgressDescriptionText"),
)
.with(
[
P._,
P.union(FolderType.SubFolderDone, FolderType.SubFolderInProgress),
P._,
],
() => t("Files:EmptyFormSubFolderHeaderText"),
.with([P._, FolderType.SubFolderDone, P._], () =>
t("Files:EmptyFormSubFolderDoneDescriptionText"),
)
.with([P._, FolderType.SubFolderInProgress, P._], () =>
t("Files:EmptyFormSubFolderProgressDescriptionText"),
)
.with(
[
@ -130,16 +127,19 @@ export const getTitle = (
if (isFolder) {
return match([parentRoomType, folderType, access])
.with([P._, FolderType.Done, P._], () =>
t("Files:EmptyFormFolderDoneDescriptionText"),
t("Files:EmptyFormFolderDoneHeaderText"),
)
.with([P._, FolderType.InProgress, P._], () =>
t("Files:EmptyFormFolderProgressDescriptionText"),
t("Files:EmptyFormFolderProgressHeaderText"),
)
.with([P._, FolderType.SubFolderDone, P._], () =>
t("Files:EmptyFormSubFolderDoneDescriptionText"),
)
.with([P._, FolderType.SubFolderInProgress, P._], () =>
t("Files:EmptyFormSubFolderProgressDescriptionText"),
.with(
[
P._,
P.union(FolderType.SubFolderDone, FolderType.SubFolderInProgress),
P._,
],
() => t("Files:EmptyFormSubFolderHeaderText"),
)
.with(
[

View File

@ -60,8 +60,9 @@ const SimulatePassword = memo(
isDisabled = false,
hasError = false,
forwardedRef,
inputValue,
}) => {
const [password, setPassword] = useState("");
const [password, setPassword] = useState(inputValue ?? "");
const [caretPosition, setCaretPosition] = useState();
const [inputType, setInputType] = useState("password");
@ -143,6 +144,10 @@ const SimulatePassword = memo(
isDisabled && inputType !== "password" && setInputType("password");
}, [isDisabled]);
useEffect(() => {
setPassword(inputValue);
}, [inputValue]);
return (
<StyledBody
className="conversation-password-wrapper"

View File

@ -337,10 +337,14 @@ export default inject<TStore>(
} = filesStore;
const files = items
? items.filter((f) => (f.fileExst || f.contentLength) && f)
? items.filter((f) => {
if (f.isFile || f.fileExst || f.contentLength) return f;
})
: [];
const folders = items
? items.filter((f) => !f.fileExst && !f.contentLength && f)
? items.filter((f) => {
if (!f.fileExst && !f.contentLength && !f.isFile) return f;
})
: [];
return {

View File

@ -56,10 +56,9 @@ const LogoutAllSessionDialog = ({
visible={visible}
onClose={onClose}
displayType="modal"
autoMaxHeight
>
<ModalDialog.Header>
{t("Profile:LogoutAllActiveConnections")}
</ModalDialog.Header>
<ModalDialog.Header>{t("Common:LogoutButton")}</ModalDialog.Header>
<ModalDialog.Body>
<Text>{t("Profile:LogoutDescription")}</Text>
<Text style={{ margin: "15px 0" }}>
@ -70,8 +69,8 @@ const LogoutAllSessionDialog = ({
className="change-password"
isChecked={isChecked}
onChange={onChangeCheckbox}
label={t("Profile:ChangePasswordAfterLoggingOut")}
/>
{t("Profile:ChangePasswordAfterLoggingOut")}
</Box>
</ModalDialog.Body>
<ModalDialog.Footer>

View File

@ -24,15 +24,15 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import React, { useRef } from "react";
import ToggleBlock from "./ToggleBlock";
import { PasswordInput } from "@docspace/shared/components/password-input";
import { IconButton } from "@docspace/shared/components/icon-button";
import { Link } from "@docspace/shared/components/link";
import RefreshReactSvgUrl from "PUBLIC_DIR/images/refresh.react.svg?url";
import { FieldContainer } from "@docspace/shared/components/field-container";
import copy from "copy-to-clipboard";
import { toastr } from "@docspace/shared/components/toast";
import SimulatePassword from "../../../components/SimulatePassword";
import { getNewPassword } from "@docspace/shared/utils";
const PasswordAccessBlock = (props) => {
const {
@ -45,14 +45,12 @@ const PasswordAccessBlock = (props) => {
setIsPasswordValid,
} = props;
const passwordInputRef = useRef(null);
const onGeneratePasswordClick = () => {
passwordInputRef.current.onGeneratePassword();
const password = getNewPassword();
setPasswordValue(password);
};
const onCleanClick = () => {
passwordInputRef.current.setState((s) => ({ ...s, value: "" })); //TODO: PasswordInput bug
setPasswordValue("");
};
@ -64,8 +62,8 @@ const PasswordAccessBlock = (props) => {
}
};
const onChangePassword = (e) => {
setPasswordValue(e.target.value);
const onChangePassword = (password) => {
setPasswordValue(password);
setIsPasswordValid(true);
};
@ -80,18 +78,13 @@ const PasswordAccessBlock = (props) => {
errorMessage={t("Common:RequiredField")}
className="edit-link_password-block"
>
<PasswordInput
// scale //doesn't work
// tabIndex={3}
// simpleView
// passwordSettings={{ minLength: 0 }}
<SimulatePassword
className="edit-link_password-input"
ref={passwordInputRef}
simpleView
isDisabled={isLoading}
hasError={!isPasswordValid}
inputValue={passwordValue}
onChange={onChangePassword}
inputMaxWidth="100%"
/>
</FieldContainer>

View File

@ -110,7 +110,7 @@ export const enum PluginComponents {
export const enum PluginUsersType {
owner = "Owner",
docSpaceAdmin = "DocspaceAdmin",
docSpaceAdmin = "DocSpaceAdmin",
roomAdmin = "RoomAdmin",
collaborator = "Collaborator",
user = "User",

View File

@ -27,6 +27,7 @@
import { withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import { TTranslation } from "@docspace/shared/types";
import { FolderType } from "@docspace/shared/enums";
import { StyledHistoryBlockMessage } from "../../../styles/history";
type HistoryMainTextFolderInfoProps = {
@ -54,12 +55,15 @@ const HistoryMainTextFolderInfo = ({
if (!parentTitle) return null;
const isFolder = parentType === 0;
const isFromFolder = fromParentType === 0;
const isSection = parentType === FolderType.USER;
const isFolder = parentType === FolderType.DEFAULT;
const isFromFolder = fromParentType === FolderType.DEFAULT;
const destination = isFolder
? t("FeedLocationLabel", { folderTitle: parentTitle })
: t("FeedLocationRoomLabel", { folderTitle: parentTitle });
: isSection
? t("FeedLocationSectionLabel", { folderTitle: parentTitle })
: t("FeedLocationRoomLabel", { folderTitle: parentTitle });
const sourceDestination = isFromFolder
? t("FeedLocationLabelFrom", { folderTitle: fromParentTitle })

View File

@ -141,25 +141,17 @@ class PeopleTableHeader extends React.Component {
const { filter, setFilter, setIsLoading, navigate, location } = this.props;
const newFilter = filter.clone();
if (newFilter.sortBy === sortBy && sortBy !== "AZ") {
if (
newFilter.sortBy === sortBy ||
(sortBy === "AZ" && newFilter.sortBy === "firstname")
) {
newFilter.sortOrder =
newFilter.sortOrder === "ascending" ? "descending" : "ascending";
} else {
newFilter.sortBy = sortBy;
if (sortBy === "AZ") {
if (
newFilter.sortBy !== "lastname" &&
newFilter.sortBy !== "firstname"
) {
newFilter.sortBy = "firstname";
} else if (newFilter.sortBy === "lastname") {
newFilter.sortBy = "firstname";
} else {
newFilter.sortBy = "lastname";
}
newFilter.sortOrder =
newFilter.sortOrder === "ascending" ? "descending" : "ascending";
newFilter.sortBy = "firstname";
}
}

View File

@ -196,6 +196,7 @@ const InfiniteGrid = (props) => {
key={key}
className={`tiles-loader ${type}`}
isFolder={type === "isFolder"}
isRoom={type === "isRoom"}
/>,
);
}

View File

@ -116,74 +116,76 @@ const RoomPassword = (props) => {
return (
<StyledPage>
<StyledContent className="public-room-content">
<StyledBody>
<PortalLogo className="portal-logo" />
<div className="public-room-page">
<StyledContent className="public-room-content">
<StyledBody>
<PortalLogo className="portal-logo" />
<FormWrapper>
<div className="password-form">
<Text fontSize="16px" fontWeight="600">
{t("UploadPanel:EnterPassword")}
</Text>
<Text
fontSize="13px"
fontWeight="400"
className="public-room-text"
>
{t("Common:NeedPassword")}:
</Text>
<div className="public-room-name">
<PublicRoomIcon className="public-room-icon" />
<Text
className="public-room-text"
fontSize="15px"
fontWeight="600"
>
{roomTitle}
<FormWrapper>
<div className="password-form">
<Text fontSize="16px" fontWeight="600">
{t("UploadPanel:EnterPassword")}
</Text>
<Text
fontSize="13px"
fontWeight="400"
className="public-room-text"
>
{t("Common:NeedPassword")}:
</Text>
<div className="public-room-name">
<PublicRoomIcon className="public-room-icon" />
<Text
className="public-room-text"
fontSize="15px"
fontWeight="600"
>
{roomTitle}
</Text>
</div>
<FieldContainer
isVertical={true}
labelVisible={false}
hasError={!!errorMessage}
errorMessage={errorMessage}
>
<PasswordInput
simpleView
id="password"
inputName="password"
placeholder={t("Common:Password")}
type="password"
inputValue={password}
hasError={!!errorMessage}
size="large"
scale
tabIndex={1}
autoComplete="current-password"
onChange={onChangePassword}
onKeyDown={onKeyPress}
isDisabled={isLoading}
isDisableTooltip
forwardedRef={inputRef}
isAutoFocussed
/>
</FieldContainer>
</div>
<FieldContainer
isVertical={true}
labelVisible={false}
hasError={!!errorMessage}
errorMessage={errorMessage}
>
<PasswordInput
simpleView
id="password"
inputName="password"
placeholder={t("Common:Password")}
type="password"
inputValue={password}
hasError={!!errorMessage}
size="large"
scale
tabIndex={1}
autoComplete="current-password"
onChange={onChangePassword}
onKeyDown={onKeyPress}
isDisabled={isLoading}
isDisableTooltip
forwardedRef={inputRef}
isAutoFocussed
/>
</FieldContainer>
</div>
<Button
primary
size="medium"
scale
label={t("Common:ContinueButton")}
tabIndex={5}
onClick={onSubmit}
isDisabled={isLoading}
/>
</FormWrapper>
</StyledBody>
</StyledContent>
<Button
primary
size="medium"
scale
label={t("Common:ContinueButton")}
tabIndex={5}
onClick={onSubmit}
isDisabled={isLoading}
/>
</FormWrapper>
</StyledBody>
</StyledContent>
</div>
</StyledPage>
);
};

View File

@ -26,20 +26,32 @@
import styled, { css } from "styled-components";
import { mobile, tablet } from "@docspace/shared/utils";
import BackgroundPatternReactSvgUrl from "PUBLIC_DIR/images/background.pattern.react.svg?url";
export const StyledPage = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin: 0 auto;
max-width: 960px;
box-sizing: border-box;
width: 100%;
height: 100%;
background-image: url("${BackgroundPatternReactSvgUrl}");
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
.public-room-page {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 auto;
max-width: 960px;
box-sizing: border-box;
}
@media ${tablet} {
padding: 0 16px;
}
@media ${mobile} {
background-image: none;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
@ -64,6 +76,7 @@ export const StyledPage = styled.div`
}
.public-room-content {
width: 100%;
padding-top: 9%;
justify-content: unset;
min-height: unset;

View File

@ -188,7 +188,11 @@ class ContextOptionsStore {
};
onClickLinkFillForm = (item) => {
if (!item.startFilling && item.isPDFForm)
const isFormRoom =
this.selectedFolderStore?.roomType === RoomsType.FormRoom ||
this.selectedFolderStore?.parentRoomType === FolderType.FormRoom;
if (!item.startFilling && item.isPDFForm && !isFormRoom)
return this.dialogsStore.setFillPDFDialogData(true, item);
return this.gotoDocEditor(false, item);
@ -1553,7 +1557,7 @@ class ContextOptionsStore {
if (primaryLink) {
copyShareLink(primaryLink.sharedTo.shareLink);
item.shared
? toastr.success(t("Files:LinkSuccessfullyCopied"))
? toastr.success(t("Common:LinkSuccessfullyCopied"))
: toastr.success(t("Files:LinkSuccessfullyCreatedAndCopied"));
setShareChanged(true);
}

View File

@ -2504,6 +2504,9 @@ class FilesActionStore {
const { clearFiles, setBufferSelection } = this.filesStore;
const { clearInsideGroup, insideGroupBackUrl } =
this.peopleStore.groupsStore;
const { isLoading } = this.clientLoadingStore;
if (isLoading) return;
setBufferSelection(null);

View File

@ -2176,7 +2176,7 @@ class FilesStore {
fileOptions = this.removeOptions(fileOptions, ["download"]);
}
if (!isPdf || item.startFilling || item.isForm) {
if (!isPdf || (shouldFillForm && canFillForm)) {
fileOptions = this.removeOptions(fileOptions, ["open-pdf"]);
}

View File

@ -586,7 +586,6 @@ class SettingsSetupStore {
};
getSessions = () => {
if (this.sessionsIsInit) return;
this.getAllSessions().then((res) => {
this.setSessions(res.items);
this.currentSession = res.loginEvent;

View File

@ -693,7 +693,10 @@ class UploadDataStore {
let conflicts = await checkIsFileExist(toFolderId, filesArray);
const folderInfo = await getFolderInfo(toFolderId);
conflicts = conflicts.map((fileTitle) => ({ title: fileTitle }));
conflicts = conflicts.map((fileTitle) => ({
title: fileTitle,
isFile: true,
}));
if (conflicts.length > 0) {
this.setConflictDialogData(conflicts, {

View File

@ -2,5 +2,6 @@
"DeepLinkText": "You can open the document on the portal or in the mobile application",
"OpenInApp": "Open in the app",
"OpeningDocument": "Opening a document",
"StayInBrowser": "Stay in the browser"
"StayInBrowser": "Stay in the browser",
"RememberChoice": "Remember the choice when opening documents next time"
}

View File

@ -103,6 +103,7 @@ const Root = ({
const { filesSettings } = useFilesSettings({});
const { socketHelper } = useSocketHelper({
socketUrl: user ? settings?.socketUrl ?? "" : "",
user,
});
const {
onSDKRequestSaveAs,

View File

@ -118,7 +118,7 @@ const DeepLink = ({
</StyledBodyWrapper>
<StyledActionsWrapper>
<Checkbox
label={t("Common:Remember")}
label={t("DeepLink:RememberChoice")}
isChecked={isRemember}
onChange={onChangeCheckbox}
/>

View File

@ -43,6 +43,7 @@ export const StyledPage = styled.div`
.logo-wrapper {
display: block;
padding-bottom: 64px;
}
@media ${mobile} {
@ -85,6 +86,17 @@ export const StyledPage = styled.div`
margin-bottom: 32px;
}
.public-room_content-wrapper {
display: flex;
flex-direction: column;
-webkit-box-align: center;
align-items: center;
margin: 0px auto;
max-width: 960px;
box-sizing: border-box;
height: 100%;
}
.public-room-content {
padding-top: 9%;
justify-content: unset;

View File

@ -26,7 +26,7 @@
"use client";
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { Trans, useTranslation } from "react-i18next";
import { Text } from "@docspace/shared/components/text";
@ -49,12 +49,13 @@ import { toastr } from "@docspace/shared/components/toast";
import { TData } from "@docspace/shared/components/toast/Toast.type";
import { getLogoUrl } from "@docspace/shared/utils";
import { frameCallCommand } from "@docspace/shared/utils/common";
import { useTheme } from "styled-components";
import { ValidationStatus, WhiteLabelLogoType } from "@docspace/shared/enums";
import { validatePublicRoomPassword } from "@docspace/shared/api/rooms";
import Image from "next/image";
const FilesPassword = ({ shareKey, title, entryTitle }: FilePasswordProps) => {
const FilePassword = ({ shareKey, title, entryTitle }: FilePasswordProps) => {
const { t } = useTranslation(["Common"]);
const theme = useTheme();
@ -64,6 +65,8 @@ const FilesPassword = ({ shareKey, title, entryTitle }: FilePasswordProps) => {
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
useEffect(() => frameCallCommand("setIsLoaded"), []);
const onChangePassword = (e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
!passwordValid && setPasswordValid(true);
@ -111,7 +114,7 @@ const FilesPassword = ({ shareKey, title, entryTitle }: FilePasswordProps) => {
return (
<>
<StyledSimpleNav id="login-header">
<StyledSimpleNav id="public-room-password-header">
<Image
className="logo"
src={logoUrl}
@ -122,90 +125,92 @@ const FilesPassword = ({ shareKey, title, entryTitle }: FilePasswordProps) => {
/>
</StyledSimpleNav>
<StyledPage>
<StyledContent className="public-room-content">
<StyledBody>
<Image
priority
src={logoUrl}
className="logo-wrapper"
alt="icon"
width={416}
height={200}
/>
<div className="public-room_content-wrapper">
<StyledContent className="public-room-content">
<StyledBody>
<Image
priority
src={logoUrl}
className="logo-wrapper"
alt="icon"
width={386}
height={44}
/>
<FormWrapper>
<div className="password-form">
<Text fontSize="16px" fontWeight="600">
{t("Common:PasswordRequired")}
</Text>
<Text
fontSize="13px"
fontWeight="400"
className="public-room-text"
>
<Trans
t={t}
ns="Common"
i18nKey="EnterPasswordDescription"
values={{ fileName: entryTitle }}
components={{ 1: <span className="bold" /> }}
/>
</Text>
<div className="public-room-name">
<PublicRoomIcon className="public-room-icon" />
<Text
className="public-room-text"
fontSize="15px"
fontWeight="600"
>
{title}
<FormWrapper>
<div className="password-form">
<Text fontSize="16px" fontWeight="600">
{t("Common:PasswordRequired")}
</Text>
<Text
fontSize="13px"
fontWeight="400"
className="public-room-text"
>
<Trans
t={t}
ns="Common"
i18nKey="EnterPasswordDescription"
values={{ fileName: entryTitle }}
components={{ 1: <span className="bold" /> }}
/>
</Text>
<div className="public-room-name">
<PublicRoomIcon className="public-room-icon" />
<Text
className="public-room-text"
fontSize="15px"
fontWeight="600"
>
{title}
</Text>
</div>
<FieldContainer
isVertical={true}
labelVisible={false}
hasError={!!errorMessage}
errorMessage={errorMessage}
>
<PasswordInput
simpleView
id="password"
inputName="password"
placeholder={t("Common:Password")}
type={InputType.password}
inputValue={password}
hasError={!!errorMessage}
size={InputSize.large}
scale
tabIndex={1}
autoComplete="current-password"
onChange={onChangePassword}
onKeyDown={onKeyPress}
isDisabled={isLoading}
isDisableTooltip
isAutoFocussed
// forwardedRef={inputRef}
/>
</FieldContainer>
</div>
<FieldContainer
isVertical={true}
labelVisible={false}
hasError={!!errorMessage}
errorMessage={errorMessage}
>
<PasswordInput
simpleView
id="password"
inputName="password"
placeholder={t("Common:Password")}
type={InputType.password}
inputValue={password}
hasError={!!errorMessage}
size={InputSize.large}
scale
tabIndex={1}
autoComplete="current-password"
onChange={onChangePassword}
onKeyDown={onKeyPress}
isDisabled={isLoading}
isDisableTooltip
isAutoFocussed
// forwardedRef={inputRef}
/>
</FieldContainer>
</div>
<Button
primary
size={ButtonSize.medium}
scale
label={t("Common:ContinueButton")}
tabIndex={5}
onClick={onSubmit}
isDisabled={isLoading}
/>
</FormWrapper>
</StyledBody>
</StyledContent>
<Button
primary
size={ButtonSize.medium}
scale
label={t("Common:ContinueButton")}
tabIndex={5}
onClick={onSubmit}
isDisabled={isLoading}
/>
</FormWrapper>
</StyledBody>
</StyledContent>
</div>
</StyledPage>
</>
);
};
export default FilesPassword;
export default FilePassword;

View File

@ -29,12 +29,14 @@
import React from "react";
import SocketIOHelper from "@docspace/shared/utils/socket";
import { combineUrl } from "@docspace/shared/utils/combineUrl";
import { getRestoreProgress } from "@docspace/shared/api/portal";
import { getUser } from "@docspace/shared/api/people";
import { EDITOR_ID } from "@docspace/shared/constants";
import { UseSocketHelperProps } from "@/types";
const useSocketHelper = ({ socketUrl }: UseSocketHelperProps) => {
const useSocketHelper = ({ socketUrl, user }: UseSocketHelperProps) => {
const [socketHelper, setSocketHelper] = React.useState<SocketIOHelper | null>(
null,
);
@ -48,6 +50,11 @@ const useSocketHelper = ({ socketUrl }: UseSocketHelperProps) => {
data: { roomParts: "backup-restore" },
});
socketIOHelper.emit({
command: "subscribe",
data: { roomParts: user?.id || "" },
});
socketIOHelper.on("restore-backup", async () => {
try {
const response = await getRestoreProgress();
@ -69,6 +76,24 @@ const useSocketHelper = ({ socketUrl }: UseSocketHelperProps) => {
}
});
socketIOHelper.on("s:logout-session", async (loginEventId) => {
console.log(`[WS] "logout-session"`, loginEventId, user?.loginEventId);
if (
Number(loginEventId) === user?.loginEventId ||
Number(loginEventId) === 0
) {
const docEditor =
typeof window !== "undefined" &&
window.DocEditor?.instances[EDITOR_ID];
docEditor?.requestClose();
window.location.replace(
combineUrl(window.ClientConfig?.proxy?.url, "/login"),
);
}
});
setSocketHelper(socketIOHelper);
}, [socketHelper, socketUrl]);
@ -76,4 +101,3 @@ const useSocketHelper = ({ socketUrl }: UseSocketHelperProps) => {
};
export default useSocketHelper;

View File

@ -334,6 +334,7 @@ export interface SelectFileDialogProps {
export interface UseSocketHelperProps {
socketUrl: string;
user?: TUser;
}
export interface UseEventsProps {

View File

@ -71,6 +71,7 @@ export type TUser = {
cultureName?: string;
groups?: TUserGroup[];
shared?: boolean;
loginEventId?: number;
};
export type TGetUserList = {

View File

@ -24,6 +24,7 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { TError } from "../../utils/axiosClient";
import { TariffState } from "../../enums";
export type TQuotas = { id: number; quantity: number };
@ -120,5 +121,5 @@ export type TTenantExtraRes = {
export type TRestoreProgress = {
progress: number;
error?: any;
error?: TError;
};

View File

@ -28,7 +28,7 @@ import { List } from "react-virtualized";
import styled, { css } from "styled-components";
import { Base } from "../../themes";
import { mobile, tablet } from "../../utils";
import { desktop, mobile, tablet } from "../../utils";
import { TViewAs } from "../../types";
const StyledScroll = styled.div`
@ -157,4 +157,28 @@ StyledScroll.defaultProps = {
theme: Base,
};
export { StyledScroll, StyledList };
const paddingCss = css`
@media ${desktop} {
margin-inline-start: 1px;
padding-inline-end: 0;
}
@media ${tablet} {
margin-inline-start: -1px;
}
`;
const StyledItem = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(216px, 1fr));
gap: 14px 16px;
width: 100%;
@media ${tablet} {
gap: 14px;
}
${paddingCss};
`;
export { StyledScroll, StyledList, StyledItem };

View File

@ -24,27 +24,66 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import React from "react";
import { useState, useEffect } from "react";
import { isMobile } from "../../utils";
import ListComponent from "./sub-components/List";
import GridComponent from "./sub-components/Grid";
import { InfiniteLoaderProps } from "./InfiniteLoader.types";
import { MAX_INFINITE_LOADER_SHIFT } from "../../utils/device";
const InfiniteLoaderComponent = (props: InfiniteLoaderProps) => {
const { viewAs, isLoading } = props;
const [scrollTop, setScrollTop] = useState(0);
const [showSkeleton, setShowSkeleton] = useState(false);
const scroll = isMobile()
? document.querySelector("#customScrollBar .scroll-wrapper > .scroller")
: document.querySelector("#sectionScroll .scroll-wrapper > .scroller");
const onScroll = (e: Event) => {
const eventTarget = e.target as HTMLElement;
const currentScrollTop = eventTarget.scrollTop;
setScrollTop(currentScrollTop ?? 0);
const scrollShift = scrollTop - currentScrollTop;
if (
scrollShift > MAX_INFINITE_LOADER_SHIFT ||
scrollShift < -MAX_INFINITE_LOADER_SHIFT
) {
setShowSkeleton(true);
setTimeout(() => {
setShowSkeleton(false);
}, 200);
}
};
useEffect(() => {
if (scroll) scroll.addEventListener("scroll", onScroll);
return () => {
if (scroll) scroll.removeEventListener("scroll", onScroll);
};
});
if (isLoading) return null;
return viewAs === "tile" ? (
<GridComponent scroll={scroll ?? window} {...props} />
<GridComponent
scroll={scroll ?? window}
showSkeleton={showSkeleton}
{...props}
/>
) : (
<ListComponent scroll={scroll ?? window} {...props} />
<ListComponent
scroll={scroll ?? window}
showSkeleton={showSkeleton}
{...props}
/>
);
};

View File

@ -42,6 +42,7 @@ export interface InfiniteLoaderProps {
className?: string;
infoPanelVisible?: boolean;
countTilesInRow?: number;
showSkeleton?: boolean;
}
export interface ListComponentProps extends InfiniteLoaderProps {

View File

@ -26,8 +26,10 @@
import React, { useCallback, useEffect, useRef } from "react";
import { InfiniteLoader, WindowScroller, List } from "react-virtualized";
import { StyledList } from "../InfiniteLoader.styled";
import { StyledItem, StyledList } from "../InfiniteLoader.styled";
import { GridComponentProps } from "../InfiniteLoader.types";
import { TileSkeleton } from "../../../skeletons/tiles";
import { RectangleSkeleton } from "../../../skeletons";
const GridComponent = ({
hasMoreFiles,
@ -39,12 +41,13 @@ const GridComponent = ({
children,
className,
scroll,
showSkeleton,
}: GridComponentProps) => {
const loaderRef = useRef<InfiniteLoader | null>(null);
const listRef = useRef<List | null>(null);
useEffect(() => {
listRef?.current?.recomputeRowHeights();
// listRef?.current?.recomputeRowHeights(); //TODO: return there will be problems with the height of the tile when clicking on the backspace
});
const isItemLoaded = useCallback(
@ -58,11 +61,50 @@ const GridComponent = ({
index,
style,
key,
isScrolling,
}: {
index: number;
style: React.CSSProperties;
key: string;
isScrolling: boolean;
}) => {
const elem = children[index] as React.ReactElement;
const itemClassNames = elem.props?.className;
const isFolder = itemClassNames?.includes("isFolder");
const isRoom = itemClassNames?.includes("isRoom");
const isHeader =
itemClassNames?.includes("folder_header") ||
itemClassNames?.includes("files_header");
if (isScrolling && showSkeleton) {
const list = [];
let i = 0;
if (isHeader) {
return (
<div key={key} style={style}>
<StyledItem>
<RectangleSkeleton height="22px" width="100px" animate />
</StyledItem>
</div>
);
}
while (i < countTilesInRow) {
list.push(
<TileSkeleton key={key} isFolder={isFolder} isRoom={isRoom} />,
);
i += 1;
}
return (
<div key={key} style={style}>
<StyledItem>{list.map((item) => item)}</StyledItem>
</div>
);
}
return (
<div className="window-item" style={style} key={key}>
{children[index]}

View File

@ -47,6 +47,7 @@ const ListComponent = ({
className,
scroll,
infoPanelVisible,
showSkeleton,
}: ListComponentProps) => {
const loaderRef = useRef<InfiniteLoader | null>(null);
const listRef = useRef<List | null>(null);
@ -89,13 +90,16 @@ const ListComponent = ({
key,
index,
style,
isScrolling,
}: {
key: string;
index: number;
style: React.CSSProperties;
isScrolling: boolean;
}) => {
const isLoaded = isItemLoaded({ index });
if (!isLoaded) return getLoader(style, key);
if (!isLoaded || (isScrolling && showSkeleton))
return getLoader(style, key);
return (
<div className="row-list-item window-item" style={style} key={key}>
@ -108,10 +112,12 @@ const ListComponent = ({
index,
style,
key,
isScrolling,
}: {
index: number;
style: React.CSSProperties;
key: string;
isScrolling: boolean;
}) => {
if (!columnInfoPanelStorageName || !columnStorageName) {
throw new Error("columnStorageName is required for a table view");
@ -122,7 +128,8 @@ const ListComponent = ({
: localStorage.getItem(columnStorageName);
const isLoaded = isItemLoaded({ index });
if (!isLoaded) return getLoader(style, key);
if (!isLoaded || (isScrolling && showSkeleton))
return getLoader(style, key);
return (
<div

View File

@ -111,6 +111,7 @@ export const ImageViewer = ({
const toolbarRef = useRef<ImperativeHandle>(null);
const [scale, setScale] = useState(1);
const [showOriginSrc, setShowOriginSrc] = useState(false);
const [isError, setIsError] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [backgroundBlack, setBackgroundBlack] = useState<boolean>(() => false);
@ -854,18 +855,19 @@ export const ImageViewer = ({
}
};
const onError = useCallback(
(e: SyntheticEvent<HTMLImageElement, Event>) => {
if (window.ClientConfig?.imageThumbnails && thumbnailSrc && src) {
// if thumbnailSrc is unavailable, try to load original image
e.currentTarget.src = src;
return;
}
const onError = useCallback(() => {
if (
window.ClientConfig?.imageThumbnails &&
thumbnailSrc &&
(src || isTiff)
) {
// if thumbnailSrc is unavailable, try to load original image
setShowOriginSrc(true);
return;
}
setIsError(true);
},
[src, thumbnailSrc],
);
setIsError(true);
}, [src, thumbnailSrc, isTiff]);
const model = React.useMemo(() => contextModel(true), [contextModel]);
@ -951,6 +953,16 @@ export const ImageViewer = ({
};
}, []);
useLayoutEffect(() => {
return () => {
if (imgRef.current) {
// abort img loading
// eslint-disable-next-line react-hooks/exhaustive-deps
imgRef.current.src = "";
}
};
}, []);
return (
<>
{isMobile && !backgroundBlack && mobileDetails}
@ -973,7 +985,9 @@ export const ImageViewer = ({
<Image
draggable="false"
src={
window.ClientConfig?.imageThumbnails && thumbnailSrc
window.ClientConfig?.imageThumbnails &&
thumbnailSrc &&
!showOriginSrc
? `${thumbnailSrc}&size=3840x2160`
: src
}

View File

@ -63,7 +63,7 @@ const StyledSelector = styled.div`
overflow: hidden;
`;
const StyledHeader = styled.div<{ withoutBorder: boolean }>`
const StyledHeader = styled.div<{ withoutBorder?: boolean }>`
width: calc(100% - 32px);
min-height: 53px;
height: 53px;

View File

@ -47,7 +47,7 @@ type THeaderBackButton =
| {
onBackClick?: undefined;
withoutBackButton?: undefined;
withoutBorder: undefined;
withoutBorder?: undefined;
};
export type TInfoBarData = {

View File

@ -154,6 +154,7 @@ const LinkRow = ({
type="onlyIcon"
isDisabled={isExpiredLink || isLoaded}
manualWidth="fit-content"
withBackdrop={false}
/>
</div>
</StyledLinkRow>

View File

@ -79,6 +79,7 @@ class TableHeaderComponent extends React.Component<
columnInfoPanelStorageName,
sortBy,
sorted,
resetColumnsSize,
} = this.props;
if (columnStorageName === prevProps.columnStorageName) {
@ -106,6 +107,13 @@ class TableHeaderComponent extends React.Component<
}
}
const storageSize =
!resetColumnsSize && localStorage.getItem(columnStorageName);
if (columns.length !== prevProps.columns.length && !storageSize) {
return this.resetColumns();
}
this.onResize();
}

View File

@ -44,6 +44,7 @@ const useLoadersHelper = () => {
const [isFirstLoad, setIsFirstLoad] = React.useState(true);
const startLoader = React.useRef<Date | null>(new Date());
const loaderTimeout = React.useRef<NodeJS.Timeout | null>(null);
const breadCrumbsLoaderTimeout = React.useRef<NodeJS.Timeout | null>(null);
const breadCrumbsStartLoader = React.useRef<Date | null>(new Date());
@ -59,9 +60,10 @@ const useLoadersHelper = () => {
const calculateLoader = React.useCallback(() => {
if (isFirstLoad) {
setShowLoader(true);
startLoader.current = new Date();
loaderTimeout.current = setTimeout(() => {
startLoader.current = new Date();
if (isMount.current) setShowLoader(true);
}, SHOW_LOADER_TIMER);
} else if (startLoader.current) {
const currentDate = new Date();
@ -80,6 +82,11 @@ const useLoadersHelper = () => {
setShowLoader(false);
}
}, MIN_LOADER_TIMER - ms);
loaderTimeout.current = null;
} else if (loaderTimeout.current) {
clearTimeout(loaderTimeout.current);
loaderTimeout.current = null;
}
}, [isFirstLoad]);

View File

@ -238,7 +238,7 @@ const FilesSelectorComponent = ({
setSelectedItemType(undefined);
getRootData();
} else {
setItems([]);
// setItems([]);
setBreadCrumbs((bc) => {
const idx = bc.findIndex(
@ -302,7 +302,7 @@ const FilesSelectorComponent = ({
if (item.isFolder) {
setIsFirstLoad(true);
setItems([]);
// setItems([]);
setBreadCrumbs((value) => [
...value,
{
@ -445,7 +445,14 @@ const FilesSelectorComponent = ({
selectedFileInfo,
);
},
[breadCrumbs, selectedFileInfo, selectedItemId, selectedTreeNode, onSubmit],
[
breadCrumbs,
rootFolderType,
onSubmit,
selectedItemId,
selectedTreeNode,
selectedFileInfo,
],
);
React.useEffect(() => {
@ -499,7 +506,7 @@ const FilesSelectorComponent = ({
submitButtonLabel,
submitButtonId,
disableSubmitButton: getIsDisabled(
isFirstLoad,
isFirstLoad && showLoader,
isSelectedParentFolder,
selectedItemId,
selectedItemType,

View File

@ -27,11 +27,19 @@
import React from "react";
import { RectangleSkeleton } from "@docspace/shared/skeletons";
import { StyledTile, StyledBottom, StyledMainContent } from "./Tiles.styled";
import {
StyledTile,
StyledBottom,
StyledMainContent,
StyledRoomTile,
StyledRoomTileTopContent,
StyledRoomTileBottomContent,
} from "./Tiles.styled";
import type { TileSkeletonProps } from "./Tiles.types";
export const TileSkeleton = ({
isFolder,
isRoom,
title,
borderRadius,
backgroundColor,
@ -84,6 +92,79 @@ export const TileSkeleton = ({
/>
</StyledBottom>
</StyledTile>
) : isRoom ? (
<StyledTile {...rest}>
<StyledRoomTile>
<StyledRoomTileTopContent>
<RectangleSkeleton
className="first-content"
title={title}
width="32px"
height="32px"
borderRadius={borderRadius}
backgroundColor={backgroundColor}
foregroundColor={foregroundColor}
backgroundOpacity={backgroundOpacity}
foregroundOpacity={foregroundOpacity}
speed={speed}
animate
/>
<RectangleSkeleton
className="second-content"
title={title}
height="22px"
borderRadius={borderRadius}
backgroundColor={backgroundColor}
foregroundColor={foregroundColor}
backgroundOpacity={backgroundOpacity}
foregroundOpacity={foregroundOpacity}
speed={speed}
animate
/>
<RectangleSkeleton
className="option-button"
title={title}
height="16px"
width="16px"
borderRadius={borderRadius}
backgroundColor={backgroundColor}
foregroundColor={foregroundColor}
backgroundOpacity={backgroundOpacity}
foregroundOpacity={foregroundOpacity}
speed={speed}
animate
/>
</StyledRoomTileTopContent>
<StyledRoomTileBottomContent>
<RectangleSkeleton
className="main-content"
title={title}
height="24px"
width="50px"
borderRadius={borderRadius}
backgroundColor={backgroundColor}
foregroundColor={foregroundColor}
backgroundOpacity={backgroundOpacity}
foregroundOpacity={foregroundOpacity}
speed={speed}
animate
/>
<RectangleSkeleton
className="main-content"
title={title}
height="24px"
width="50px"
borderRadius={borderRadius}
backgroundColor={backgroundColor}
foregroundColor={foregroundColor}
backgroundOpacity={backgroundOpacity}
foregroundOpacity={foregroundOpacity}
speed={speed}
animate
/>
</StyledRoomTileBottomContent>
</StyledRoomTile>
</StyledTile>
) : (
<StyledTile {...rest}>
<StyledMainContent>

View File

@ -120,3 +120,26 @@ export const StyledTilesWrapper = styled.div`
grid-template-columns: 1fr;
grid-gap: 16px;
`;
export const StyledRoomTile = styled.div`
border: ${(props) => props.theme.filesSection.tilesView.tile.border};
border-radius: 6px;
height: 120px;
`;
export const StyledRoomTileTopContent = styled.div`
display: grid;
grid-template-columns: 32px 1fr 24px;
gap: 8px;
align-items: center;
height: 61px;
border-bottom: ${(props) => props.theme.filesSection.tilesView.tile.border};
padding: 0 8px 0 16px;
`;
export const StyledRoomTileBottomContent = styled.div`
display: flex;
align-items: center;
padding: 16px;
gap: 4px;
`;

View File

@ -38,4 +38,5 @@ export interface StyledBottomProps {
export interface TileSkeletonProps extends RectangleSkeletonProps {
isFolder?: boolean;
isRoom?: boolean;
}

View File

@ -26,6 +26,7 @@
export const INFO_PANEL_WIDTH = 400;
export const TABLE_HEADER_HEIGHT = 40;
export const MAX_INFINITE_LOADER_SHIFT = 800;
export function checkIsSSR() {
return typeof window === "undefined";

View File

@ -183,3 +183,66 @@ export const getLastColumn = (tableStorageName: string) => {
return null;
};
export const getNewPassword = (
settings: {
minLength?: number;
upperCase?: boolean;
digits?: boolean;
specSymbols?: boolean;
digitsRegexStr?: string;
upperCaseRegexStr?: string;
specSymbolsRegexStr?: string;
allowedCharactersRegexStr?: string;
},
generatorSpecial: string = "!@#$%^&*",
) => {
const passwordSettings = settings ?? {
minLength: 8,
upperCase: false,
digits: false,
specSymbols: false,
digitsRegexStr: "(?=.*\\d)",
upperCaseRegexStr: "(?=.*[A-Z])",
specSymbolsRegexStr: "(?=.*[\\x21-\\x2F\\x3A-\\x40\\x5B-\\x60\\x7B-\\x7E])",
};
const length = passwordSettings?.minLength || 0;
const string = "abcdefghijklmnopqrstuvwxyz";
const numeric = "0123456789";
const special = generatorSpecial || "";
let password = "";
let character = "";
while (password.length < length) {
const a = Math.ceil(string.length * Math.random() * Math.random());
const b = Math.ceil(numeric.length * Math.random() * Math.random());
const c = Math.ceil(special.length * Math.random() * Math.random());
let hold = string.charAt(a);
if (passwordSettings?.upperCase) {
hold = password.length % 2 === 0 ? hold.toUpperCase() : hold;
}
character += hold;
if (passwordSettings?.digits) {
character += numeric.charAt(b);
}
if (passwordSettings?.specSymbols) {
character += special.charAt(c);
}
password = character;
}
password = password
.split("")
.sort(() => 0.5 - Math.random())
.join("");
return password.substring(0, length);
};