diff --git a/packages/client/public/locales/en/InfoPanel.json b/packages/client/public/locales/en/InfoPanel.json index 105dbdb5c1..f295708b17 100644 --- a/packages/client/public/locales/en/InfoPanel.json +++ b/packages/client/public/locales/en/InfoPanel.json @@ -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}}»", diff --git a/packages/client/src/Shell.jsx b/packages/client/src/Shell.jsx index bf0165745d..9523d6901a 100644 --- a/packages/client/src/Shell.jsx +++ b/packages/client/src/Shell.jsx @@ -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, diff --git a/packages/client/src/components/Badges.js b/packages/client/src/components/Badges.js index ed867031fe..506d6396bd 100644 --- a/packages/client/src/components/Badges.js +++ b/packages/client/src/components/Badges.js @@ -254,7 +254,7 @@ const Badges = ({ )} - {isEditing && !isVisitor && !(isRecentTab && !canEditing) && ( + {isEditing && !(isRecentTab && !canEditing) && ( - 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( [ diff --git a/packages/client/src/components/SimulatePassword/index.js b/packages/client/src/components/SimulatePassword/index.js index 93e6072739..395e497177 100644 --- a/packages/client/src/components/SimulatePassword/index.js +++ b/packages/client/src/components/SimulatePassword/index.js @@ -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 ( ( } = 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 { diff --git a/packages/client/src/components/dialogs/LogoutAllSessionDialog/index.js b/packages/client/src/components/dialogs/LogoutAllSessionDialog/index.js index 64186f2809..1b2245c8c2 100644 --- a/packages/client/src/components/dialogs/LogoutAllSessionDialog/index.js +++ b/packages/client/src/components/dialogs/LogoutAllSessionDialog/index.js @@ -56,10 +56,9 @@ const LogoutAllSessionDialog = ({ visible={visible} onClose={onClose} displayType="modal" + autoMaxHeight > - - {t("Profile:LogoutAllActiveConnections")} - + {t("Common:LogoutButton")} {t("Profile:LogoutDescription")} @@ -70,8 +69,8 @@ const LogoutAllSessionDialog = ({ className="change-password" isChecked={isChecked} onChange={onChangeCheckbox} + label={t("Profile:ChangePasswordAfterLoggingOut")} /> - {t("Profile:ChangePasswordAfterLoggingOut")} diff --git a/packages/client/src/components/panels/EditLinkPanel/PasswordAccessBlock.js b/packages/client/src/components/panels/EditLinkPanel/PasswordAccessBlock.js index c96c152747..aa3216f61a 100644 --- a/packages/client/src/components/panels/EditLinkPanel/PasswordAccessBlock.js +++ b/packages/client/src/components/panels/EditLinkPanel/PasswordAccessBlock.js @@ -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" > - diff --git a/packages/client/src/pages/Home/InfoPanel/Body/index.js b/packages/client/src/pages/Home/InfoPanel/Body/index.js index cc80317b61..6498a1412a 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/index.js +++ b/packages/client/src/pages/Home/InfoPanel/Body/index.js @@ -74,7 +74,9 @@ const InfoPanelBodyContent = ({ selectedItems[0]?.membersCount !== null && selectedItems[0]?.membersCount !== undefined )) || - (isInsideGroup && selectedItems.length && !selectedItems[0].manager); + (isInsideGroup && + selectedItems.length && + !selectedItems[0].hasOwnProperty("membersCount")); const isSeveralItems = props.selectedItems?.length > 1; diff --git a/packages/client/src/pages/Home/InfoPanel/Body/views/History/HistoryBlockContent/MainTextFolderInfo.tsx b/packages/client/src/pages/Home/InfoPanel/Body/views/History/HistoryBlockContent/MainTextFolderInfo.tsx index 05a4f70a4d..2355bca26a 100644 --- a/packages/client/src/pages/Home/InfoPanel/Body/views/History/HistoryBlockContent/MainTextFolderInfo.tsx +++ b/packages/client/src/pages/Home/InfoPanel/Body/views/History/HistoryBlockContent/MainTextFolderInfo.tsx @@ -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 }) diff --git a/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/InfiniteGrid.js b/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/InfiniteGrid.js index f3104c9907..625170db8e 100644 --- a/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/InfiniteGrid.js +++ b/packages/client/src/pages/Home/Section/Body/TilesView/sub-components/InfiniteGrid.js @@ -196,6 +196,7 @@ const InfiniteGrid = (props) => { key={key} className={`tiles-loader ${type}`} isFolder={type === "isFolder"} + isRoom={type === "isRoom"} />, ); } diff --git a/packages/client/src/pages/PublicRoom/sub-components/RoomPassword.js b/packages/client/src/pages/PublicRoom/sub-components/RoomPassword.js index b74f86f907..539179e565 100644 --- a/packages/client/src/pages/PublicRoom/sub-components/RoomPassword.js +++ b/packages/client/src/pages/PublicRoom/sub-components/RoomPassword.js @@ -116,74 +116,76 @@ const RoomPassword = (props) => { return ( - - - +
+ + + - -
- - {t("UploadPanel:EnterPassword")} - - - - {t("Common:NeedPassword")}: - -
- - - {roomTitle} + +
+ + {t("UploadPanel:EnterPassword")} + + + {t("Common:NeedPassword")}: + +
+ + + {roomTitle} + +
+ + + +
- - - -
- -
); }; diff --git a/packages/client/src/pages/PublicRoom/sub-components/RoomStyles.js b/packages/client/src/pages/PublicRoom/sub-components/RoomStyles.js index c63e7eb4b3..37f7150b39 100644 --- a/packages/client/src/pages/PublicRoom/sub-components/RoomStyles.js +++ b/packages/client/src/pages/PublicRoom/sub-components/RoomStyles.js @@ -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; diff --git a/packages/client/src/store/ContextOptionsStore.js b/packages/client/src/store/ContextOptionsStore.js index 9d0ddf6fd1..d7a2037724 100644 --- a/packages/client/src/store/ContextOptionsStore.js +++ b/packages/client/src/store/ContextOptionsStore.js @@ -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); } diff --git a/packages/client/src/store/FilesActionsStore.js b/packages/client/src/store/FilesActionsStore.js index 4ef3e4d9d3..11fc69fd0c 100644 --- a/packages/client/src/store/FilesActionsStore.js +++ b/packages/client/src/store/FilesActionsStore.js @@ -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); diff --git a/packages/client/src/store/FilesStore.js b/packages/client/src/store/FilesStore.js index 26bcf0e656..0337a222ef 100644 --- a/packages/client/src/store/FilesStore.js +++ b/packages/client/src/store/FilesStore.js @@ -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"]); } diff --git a/packages/client/src/store/SettingsSetupStore.js b/packages/client/src/store/SettingsSetupStore.js index c42fdce4da..cc8c06f14a 100644 --- a/packages/client/src/store/SettingsSetupStore.js +++ b/packages/client/src/store/SettingsSetupStore.js @@ -586,7 +586,6 @@ class SettingsSetupStore { }; getSessions = () => { - if (this.sessionsIsInit) return; this.getAllSessions().then((res) => { this.setSessions(res.items); this.currentSession = res.loginEvent; diff --git a/packages/client/src/store/UploadDataStore.js b/packages/client/src/store/UploadDataStore.js index 51f93e16fe..34f3cdb11b 100644 --- a/packages/client/src/store/UploadDataStore.js +++ b/packages/client/src/store/UploadDataStore.js @@ -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, { diff --git a/packages/doceditor/public/locales/en/DeepLink.json b/packages/doceditor/public/locales/en/DeepLink.json index de689cd443..179c72121d 100644 --- a/packages/doceditor/public/locales/en/DeepLink.json +++ b/packages/doceditor/public/locales/en/DeepLink.json @@ -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" } diff --git a/packages/doceditor/src/components/Root.tsx b/packages/doceditor/src/components/Root.tsx index 7928e29e0e..378b9aac04 100644 --- a/packages/doceditor/src/components/Root.tsx +++ b/packages/doceditor/src/components/Root.tsx @@ -103,6 +103,7 @@ const Root = ({ const { filesSettings } = useFilesSettings({}); const { socketHelper } = useSocketHelper({ socketUrl: user ? settings?.socketUrl ?? "" : "", + user, }); const { onSDKRequestSaveAs, diff --git a/packages/doceditor/src/components/deep-link/index.tsx b/packages/doceditor/src/components/deep-link/index.tsx index 9b898f1535..7d5af01a83 100644 --- a/packages/doceditor/src/components/deep-link/index.tsx +++ b/packages/doceditor/src/components/deep-link/index.tsx @@ -118,7 +118,7 @@ const DeepLink = ({ diff --git a/packages/doceditor/src/components/file-password/FilePassword.styled.ts b/packages/doceditor/src/components/file-password/FilePassword.styled.ts index 9173ea5248..5c714e4962 100644 --- a/packages/doceditor/src/components/file-password/FilePassword.styled.ts +++ b/packages/doceditor/src/components/file-password/FilePassword.styled.ts @@ -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; diff --git a/packages/doceditor/src/components/file-password/index.tsx b/packages/doceditor/src/components/file-password/index.tsx index 231ae0d68b..78cf52078e 100644 --- a/packages/doceditor/src/components/file-password/index.tsx +++ b/packages/doceditor/src/components/file-password/index.tsx @@ -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) => { setPassword(e.target.value); !passwordValid && setPasswordValid(true); @@ -111,7 +114,7 @@ const FilesPassword = ({ shareKey, title, entryTitle }: FilePasswordProps) => { return ( <> - + { /> - - - icon +
+ + + icon - -
- - {t("Common:PasswordRequired")} - - - - }} - /> - -
- - - {title} + +
+ + {t("Common:PasswordRequired")} + + + }} + /> + +
+ + + {title} + +
+ + + +
- - - -
- -
); }; -export default FilesPassword; +export default FilePassword; diff --git a/packages/doceditor/src/hooks/useSocketHelper.ts b/packages/doceditor/src/hooks/useSocketHelper.ts index 048796ead8..9eeb1a98b7 100644 --- a/packages/doceditor/src/hooks/useSocketHelper.ts +++ b/packages/doceditor/src/hooks/useSocketHelper.ts @@ -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( 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; - diff --git a/packages/doceditor/src/types/index.ts b/packages/doceditor/src/types/index.ts index 1446d3e59a..739282d2e6 100644 --- a/packages/doceditor/src/types/index.ts +++ b/packages/doceditor/src/types/index.ts @@ -334,6 +334,7 @@ export interface SelectFileDialogProps { export interface UseSocketHelperProps { socketUrl: string; + user?: TUser; } export interface UseEventsProps { diff --git a/packages/shared/api/people/types.ts b/packages/shared/api/people/types.ts index d211f6de1a..d74b09c9b2 100644 --- a/packages/shared/api/people/types.ts +++ b/packages/shared/api/people/types.ts @@ -71,6 +71,7 @@ export type TUser = { cultureName?: string; groups?: TUserGroup[]; shared?: boolean; + loginEventId?: number; }; export type TGetUserList = { diff --git a/packages/shared/api/portal/types.ts b/packages/shared/api/portal/types.ts index 1dd34fcb05..c71e738a6d 100644 --- a/packages/shared/api/portal/types.ts +++ b/packages/shared/api/portal/types.ts @@ -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 }; @@ -129,5 +130,5 @@ export type TTenantExtra = { export type TRestoreProgress = { progress: number; - error?: any; + error?: TError; }; diff --git a/packages/shared/components/infinite-loader/InfiniteLoader.styled.ts b/packages/shared/components/infinite-loader/InfiniteLoader.styled.ts index 0fe163a976..398dd9166a 100644 --- a/packages/shared/components/infinite-loader/InfiniteLoader.styled.ts +++ b/packages/shared/components/infinite-loader/InfiniteLoader.styled.ts @@ -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 }; diff --git a/packages/shared/components/infinite-loader/InfiniteLoader.tsx b/packages/shared/components/infinite-loader/InfiniteLoader.tsx index bae74b22fe..85708e28f5 100644 --- a/packages/shared/components/infinite-loader/InfiniteLoader.tsx +++ b/packages/shared/components/infinite-loader/InfiniteLoader.tsx @@ -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" ? ( - + ) : ( - + ); }; diff --git a/packages/shared/components/infinite-loader/InfiniteLoader.types.ts b/packages/shared/components/infinite-loader/InfiniteLoader.types.ts index d1203bddf7..673cbff85d 100644 --- a/packages/shared/components/infinite-loader/InfiniteLoader.types.ts +++ b/packages/shared/components/infinite-loader/InfiniteLoader.types.ts @@ -42,6 +42,7 @@ export interface InfiniteLoaderProps { className?: string; infoPanelVisible?: boolean; countTilesInRow?: number; + showSkeleton?: boolean; } export interface ListComponentProps extends InfiniteLoaderProps { diff --git a/packages/shared/components/infinite-loader/sub-components/Grid.tsx b/packages/shared/components/infinite-loader/sub-components/Grid.tsx index 5d50bfa67f..6b872a04e3 100644 --- a/packages/shared/components/infinite-loader/sub-components/Grid.tsx +++ b/packages/shared/components/infinite-loader/sub-components/Grid.tsx @@ -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(null); const listRef = useRef(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 ( +
+ + + +
+ ); + } + + while (i < countTilesInRow) { + list.push( + , + ); + i += 1; + } + + return ( +
+ {list.map((item) => item)} +
+ ); + } + return (
{children[index]} diff --git a/packages/shared/components/infinite-loader/sub-components/List.tsx b/packages/shared/components/infinite-loader/sub-components/List.tsx index add98587e6..ed70f0b2b6 100644 --- a/packages/shared/components/infinite-loader/sub-components/List.tsx +++ b/packages/shared/components/infinite-loader/sub-components/List.tsx @@ -47,6 +47,7 @@ const ListComponent = ({ className, scroll, infoPanelVisible, + showSkeleton, }: ListComponentProps) => { const loaderRef = useRef(null); const listRef = useRef(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 (
@@ -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 (
(null); const [scale, setScale] = useState(1); + const [showOriginSrc, setShowOriginSrc] = useState(false); const [isError, setIsError] = useState(false); const [isLoading, setIsLoading] = useState(false); const [backgroundBlack, setBackgroundBlack] = useState(() => false); @@ -854,18 +855,19 @@ export const ImageViewer = ({ } }; - const onError = useCallback( - (e: SyntheticEvent) => { - 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]); @@ -983,7 +985,9 @@ export const ImageViewer = ({
diff --git a/packages/shared/components/table/TableHeader.tsx b/packages/shared/components/table/TableHeader.tsx index 63927f16af..5bd6bd2cab 100644 --- a/packages/shared/components/table/TableHeader.tsx +++ b/packages/shared/components/table/TableHeader.tsx @@ -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(); } diff --git a/packages/shared/skeletons/tiles/Tile.tsx b/packages/shared/skeletons/tiles/Tile.tsx index 0a8e2fe325..907b95dd4b 100644 --- a/packages/shared/skeletons/tiles/Tile.tsx +++ b/packages/shared/skeletons/tiles/Tile.tsx @@ -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 = ({ /> + ) : isRoom ? ( + + + + + + + + + + + + + ) : ( diff --git a/packages/shared/skeletons/tiles/Tiles.styled.ts b/packages/shared/skeletons/tiles/Tiles.styled.ts index f3ac12fa81..871cc6e965 100644 --- a/packages/shared/skeletons/tiles/Tiles.styled.ts +++ b/packages/shared/skeletons/tiles/Tiles.styled.ts @@ -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; +`; diff --git a/packages/shared/skeletons/tiles/Tiles.types.ts b/packages/shared/skeletons/tiles/Tiles.types.ts index 2881313070..982ab305bc 100644 --- a/packages/shared/skeletons/tiles/Tiles.types.ts +++ b/packages/shared/skeletons/tiles/Tiles.types.ts @@ -38,4 +38,5 @@ export interface StyledBottomProps { export interface TileSkeletonProps extends RectangleSkeletonProps { isFolder?: boolean; + isRoom?: boolean; } diff --git a/packages/shared/utils/device.ts b/packages/shared/utils/device.ts index 1900a6a3cf..5ca02c86ce 100644 --- a/packages/shared/utils/device.ts +++ b/packages/shared/utils/device.ts @@ -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"; diff --git a/packages/shared/utils/index.ts b/packages/shared/utils/index.ts index 6f4d74fba9..cd467ae9a9 100644 --- a/packages/shared/utils/index.ts +++ b/packages/shared/utils/index.ts @@ -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); +};