diff --git a/build/install/deb/debian/control b/build/install/deb/debian/control index ab8833e9b2..7cb3c51392 100644 --- a/build/install/deb/debian/control +++ b/build/install/deb/debian/control @@ -62,12 +62,12 @@ Depends: {{product}}-common (= {{package_header_tag_version}}), dotnet-sdk-7.0, Recommends: ffmpeg, elasticsearch (= 7.10.0) Description: {{product}}-files-services The service which launches additional services related to file management: - - ElasticSearchIndexService - indexes documents using elasticsearch; - - FeedAggregatorService - aggregates notifications; - - FeedCleanerService - removes notifications; - - FileConverterService - converts documents; - - ThumbnailBuilderService - generates thumbnails for documents; - - Launcher - removes outdated files from Trash; + - ElasticSearchIndexService - indexes documents using elasticsearch; + - FeedAggregatorService - aggregates notifications; + - FeedCleanerService - removes notifications; + - FileConverterService - converts documents; + - ThumbnailBuilderService - generates thumbnails for documents; + - Launcher - removes outdated files from Trash; Package: {{product}}-notify Architecture: any @@ -90,7 +90,7 @@ Architecture: any Depends: {{product}}-common (= {{package_header_tag_version}}), nodejs (>= 16), ${misc:Depends}, ${shlibs:Depends} Recommends: redis-server Description: {{product}}-socket - The service which provides two-way communication between a web browser and the server + The service which provides two-way communication between a client and a server Package: {{product}}-studio-notify Architecture: any @@ -112,7 +112,7 @@ Package: {{product}}-api-system Architecture: any Depends: {{product}}-common (= {{package_header_tag_version}}), dotnet-sdk-7.0, ${misc:Depends}, ${shlibs:Depends} Description: {{product}}-api-system - The service which is used for working with portals (creating, removing portals, etc.) + The service which is used for working with portals (creating, removing, etc.) Package: {{product}}-studio Architecture: any @@ -141,11 +141,11 @@ Architecture: any Depends: {{product}}-common (= {{package_header_tag_version}}), dotnet-sdk-7.0, ${misc:Depends}, ${shlibs:Depends} Description: {{product}}-backup-background The service which launches additional services related to backup creation: - - BackupWorkerService - launches WorkerService which runs backup/restore, etc.; - - BackupListenerService - waits for a signal to delete backups; - - BackupCleanerTempFileService - removes temporary backup files; - - BackupCleanerService - removes outdated backup files; - - BackupSchedulerService - runs backup according to a schedule; + - BackupWorkerService - launches WorkerService which runs backup/restore, etc; + - BackupListenerService - waits for a signal to delete backups; + - BackupCleanerTempFileService - removes temporary backup files; + - BackupCleanerService - removes outdated backup files; + - BackupSchedulerService - runs backup according to a schedule; Package: {{product}}-clear-events Architecture: any diff --git a/build/install/rpm/SPECS/package.spec b/build/install/rpm/SPECS/package.spec index a3b7720842..77ce2f5c19 100644 --- a/build/install/rpm/SPECS/package.spec +++ b/build/install/rpm/SPECS/package.spec @@ -14,7 +14,7 @@ Summary: Common Group: Applications/Internet Requires: logrotate %description common -A package containing configs and scripts +A package containing configure and scripts %package files-services Packager: %{packager} @@ -26,12 +26,12 @@ Requires: ffmpeg AutoReqProv: no %description files-services The service which launches additional services related to file management: -- ElasticSearchIndexService - indexes documents using elasticsearch; -- FeedAggregatorService - aggregates notifications; -- FeedCleanerService - removes notifications; -- FileConverterService - converts documents; -- ThumbnailBuilderService - generates thumbnails for documents; -- Launcher - removes outdated files from Trash; + - ElasticSearchIndexService - indexes documents using Elasticsearch; + - FeedAggregatorService - aggregates notifications; + - FeedCleanerService - removes notifications; + - FileConverterService - converts documents; + - ThumbnailBuilderService - generates thumbnails for documents; + - Launcher - removes outdated files from Trash; %package notify Packager: %{packager} @@ -99,7 +99,7 @@ Requires: %name-common = %version-%release Requires: nodejs >= 16.0 AutoReqProv: no %description socket -The service which provides two-way communication between a web browser and the server +The service which provides two-way communication between a client and a server %package studio Packager: %{packager} @@ -131,7 +131,7 @@ Requires: %name-common = %version-%release Requires: dotnet-sdk-7.0 AutoReqProv: no %description api-system -The service which is used for working with portals (creating, removing portals, etc.) +The service which is used for working with portals (creating, removing, etc.) %package ssoauth Packager: %{packager} @@ -141,7 +141,9 @@ Requires: %name-common = %version-%release Requires: nodejs >= 16.0 AutoReqProv: no %description ssoauth -Ssoauth +The service responsible for enabling and configuring +SAML-based single sign-on (SSO) authentication to provide a more quick, +easy and secure way to access DocSpace for users %package clear-events Packager: %{packager} @@ -151,7 +153,8 @@ Requires: %name-common = %version-%release Requires: dotnet-sdk-7.0 AutoReqProv: no %description clear-events -Clear-events +The service responsible for clearing the login_events and audit_events tables +by LoginHistoryLifeTime and AuditTrailLifeTime to log out users after a timeout %package backup-background Packager: %{packager} @@ -161,7 +164,12 @@ Requires: %name-common = %version-%release Requires: dotnet-sdk-7.0 AutoReqProv: no %description backup-background -Backup-background +The service which launches additional services related to backup creation: + - BackupWorkerService - launches WorkerService which runs backup/restore, etc; + - BackupListenerService - waits for a signal to delete backups; + - BackupCleanerTempFileService - removes temporary backup files; + - BackupCleanerService - removes outdated backup files; + - BackupSchedulerService - runs backup according to a schedule; %package radicale Packager: %{packager} @@ -174,7 +182,9 @@ Requires: python3-requests Requires: python3-setuptools AutoReqProv: no %description radicale -Radicale +Radicale is a server designed to support the CalDav and CardDav protocols. +It operates either as a standalone package using its own internal HTTP server +or can be integrated with an existing web server %package doceditor Packager: %{packager} @@ -184,7 +194,7 @@ Requires: %name-common = %version-%release Requires: nodejs >= 16.0 AutoReqProv: no %description doceditor -Doceditor +The service which allows interaction with document-server %package migration-runner Packager: %{packager} @@ -194,7 +204,9 @@ Requires: %name-common = %version-%release Requires: dotnet-sdk-7.0 AutoReqProv: no %description migration-runner -Migration-runner +The service responsible for the database creation. +A database connection is transferred to the service and +the service creates tables and populates them with values %package login Packager: %{packager} @@ -204,7 +216,7 @@ Requires: %name-common = %version-%release Requires: nodejs >= 16.0 AutoReqProv: no %description login -Login +The service which is used for logging users and displaying the wizard %package healthchecks Packager: %{packager} @@ -214,4 +226,4 @@ Requires: %name-common = %version-%release Requires: dotnet-sdk-7.0 AutoReqProv: no %description healthchecks -Healthchecks +The service which displays launched services diff --git a/common/ASC.Api.Core/ASC.Api.Core.csproj b/common/ASC.Api.Core/ASC.Api.Core.csproj index 1be535aed0..bdbb836ab1 100644 --- a/common/ASC.Api.Core/ASC.Api.Core.csproj +++ b/common/ASC.Api.Core/ASC.Api.Core.csproj @@ -23,7 +23,7 @@ - + diff --git a/common/ASC.Api.Core/Core/BaseStartup.cs b/common/ASC.Api.Core/Core/BaseStartup.cs index ab21912d57..7073c345d0 100644 --- a/common/ASC.Api.Core/Core/BaseStartup.cs +++ b/common/ASC.Api.Core/Core/BaseStartup.cs @@ -199,7 +199,7 @@ public abstract class BaseStartup config.Filters.Add(new TypeFilterAttribute(typeof(IpSecurityFilter))); config.Filters.Add(new TypeFilterAttribute(typeof(ProductSecurityFilter))); config.Filters.Add(new CustomResponseFilterAttribute()); - config.Filters.Add(new CustomExceptionFilterAttribute()); + config.Filters.Add(); config.Filters.Add(new TypeFilterAttribute(typeof(WebhooksGlobalFilterAttribute))); }); diff --git a/common/ASC.Api.Core/Middleware/LoggerMiddleware.cs b/common/ASC.Api.Core/Middleware/LoggerMiddleware.cs index 784d03a934..1a2ed0efc0 100644 --- a/common/ASC.Api.Core/Middleware/LoggerMiddleware.cs +++ b/common/ASC.Api.Core/Middleware/LoggerMiddleware.cs @@ -62,7 +62,7 @@ public class LoggerMiddleware state.Add("tenantMappedDomain", tenant.MappedDomain); } - using (logger.BeginScope(state.ToArray())) + using (logger.BeginScope(state)) { await _next.Invoke(context); } diff --git a/common/ASC.Api.Core/Middleware/ResponseWrapper.cs b/common/ASC.Api.Core/Middleware/ResponseWrapper.cs index 43d85445d6..89d841e81a 100644 --- a/common/ASC.Api.Core/Middleware/ResponseWrapper.cs +++ b/common/ASC.Api.Core/Middleware/ResponseWrapper.cs @@ -28,6 +28,13 @@ namespace ASC.Api.Core.Middleware; public class CustomExceptionFilterAttribute : ExceptionFilterAttribute { + private readonly ILogger _logger; + + public CustomExceptionFilterAttribute(ILogger logger) + { + _logger = logger; + } + public override void OnException(ExceptionContext context) { var status = (HttpStatusCode)context.HttpContext.Response.StatusCode; @@ -69,6 +76,9 @@ public class CustomExceptionFilterAttribute : ExceptionFilterAttribute break; } + _logger.LogError(exception, + $"error during executing {context.HttpContext.Request?.Method}: {context.HttpContext.Request?.Path.Value}"); + var result = new ObjectResult(new ErrorApiResponse(status, exception, message, withStackTrace)) { StatusCode = (int)status diff --git a/common/ASC.Api.Core/Middleware/UnhandledExceptionMiddleware.cs b/common/ASC.Api.Core/Middleware/UnhandledExceptionMiddleware.cs new file mode 100644 index 0000000000..07f439821b --- /dev/null +++ b/common/ASC.Api.Core/Middleware/UnhandledExceptionMiddleware.cs @@ -0,0 +1,114 @@ +// (c) Copyright Ascensio System SIA 2010-2022 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// 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 + +namespace ASC.Api.Core.Middleware; + +// problem: https://github.com/aspnet/Logging/issues/677 +public class UnhandledExceptionMiddleware +{ + private readonly RequestDelegate _next; + + public UnhandledExceptionMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext context, + ILogger logger) + { + try + { + await _next(context); + } + catch (Exception ex) when (LogError(ex)) + { + await OnException(context, ex); + } + + bool LogError(Exception ex) + { + logger.LogError(ex, + $"Request {context.Request?.Method}: {context.Request?.Path.Value} failed"); + + return true; + } + + } + + public async Task OnException(HttpContext context, Exception exception) + { + var status = (HttpStatusCode)context.Response.StatusCode; + string message = null; + + if (status == HttpStatusCode.OK) + { + status = HttpStatusCode.InternalServerError; + } + + var withStackTrace = true; + + switch (exception) + { + case ItemNotFoundException: + status = HttpStatusCode.NotFound; + message = "The record could not be found"; + break; + case ArgumentException: + status = HttpStatusCode.BadRequest; + message = "Invalid arguments"; + break; + case SecurityException: + status = HttpStatusCode.Forbidden; + message = "Access denied"; + break; + case AuthenticationException: + status = HttpStatusCode.Unauthorized; + withStackTrace = false; + break; + case InvalidOperationException: + status = HttpStatusCode.Forbidden; + break; + case TenantQuotaException: + case BillingNotFoundException: + status = HttpStatusCode.PaymentRequired; + break; + } + + var result = new ErrorApiResponse(status, exception, message, withStackTrace); + + context.Response.StatusCode = (int)status; + + await context.Response.WriteAsJsonAsync(result); + } +} + +public static class UnhandledExceptionMiddlewareExtensions +{ + public static IApplicationBuilder UseUnhandledExceptionMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } +} \ No newline at end of file diff --git a/common/ASC.Common/ASC.Common.csproj b/common/ASC.Common/ASC.Common.csproj index 92c17cedc0..e812b783ea 100644 --- a/common/ASC.Common/ASC.Common.csproj +++ b/common/ASC.Common/ASC.Common.csproj @@ -55,7 +55,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive --> - + diff --git a/common/ASC.Core.Common/ASC.Core.Common.csproj b/common/ASC.Core.Common/ASC.Core.Common.csproj index 26881c614f..1d0a2f864f 100644 --- a/common/ASC.Core.Common/ASC.Core.Common.csproj +++ b/common/ASC.Core.Common/ASC.Core.Common.csproj @@ -63,7 +63,6 @@ - diff --git a/common/services/ASC.TelegramService/ASC.TelegramService.csproj b/common/services/ASC.TelegramService/ASC.TelegramService.csproj index 6c1041f609..0c62255a4f 100644 --- a/common/services/ASC.TelegramService/ASC.TelegramService.csproj +++ b/common/services/ASC.TelegramService/ASC.TelegramService.csproj @@ -23,8 +23,4 @@ - - - - diff --git a/common/services/ASC.TelegramService/TelegramHandler.cs b/common/services/ASC.TelegramService/TelegramHandler.cs index 072d5d376c..9505701bcb 100644 --- a/common/services/ASC.TelegramService/TelegramHandler.cs +++ b/common/services/ASC.TelegramService/TelegramHandler.cs @@ -188,7 +188,7 @@ public class TelegramHandler var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken); client.StartReceiving(updateHandler: (botClient, exception, cancellationToken) => HandleUpdateAsync(botClient, exception, cancellationToken, tenantId), - errorHandler: HandleErrorAsync, + pollingErrorHandler: HandleErrorAsync, cancellationToken: linkedCts.Token); } diff --git a/config/nlog.config b/config/nlog.config index 59fec170c0..cfa177f390 100644 --- a/config/nlog.config +++ b/config/nlog.config @@ -42,7 +42,11 @@ - + + + + + diff --git a/packages/client/package.json b/packages/client/package.json index 8b50f191e2..da19486031 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -46,6 +46,7 @@ "file-saver": "^2.0.5", "firebase": "^8.10.0", "hex-to-rgba": "^2.0.1", + "queue-promise": "2.2.1", "react-avatar-editor": "^13.0.0", "react-colorful": "^5.5.1", "react-hotkeys-hook": "^3.4.4", diff --git a/packages/client/public/locales/en/Files.json b/packages/client/public/locales/en/Files.json index 5db96d143c..efa68b8b0e 100644 --- a/packages/client/public/locales/en/Files.json +++ b/packages/client/public/locales/en/Files.json @@ -113,5 +113,6 @@ "ViewList": "List", "ViewOnlyRooms": "View-only", "ViewTiles": "Tiles", - "WithSubfolders": "With subfolders" + "WithSubfolders": "With subfolders", + "DocumentEdited": "Cannot perform the action because the document is being edited." } diff --git a/packages/client/public/locales/ru/Files.json b/packages/client/public/locales/ru/Files.json index 0716c8cd3d..484a118c7d 100644 --- a/packages/client/public/locales/ru/Files.json +++ b/packages/client/public/locales/ru/Files.json @@ -113,5 +113,6 @@ "ViewList": "Список", "ViewOnlyRooms": "Просмотр", "ViewTiles": "Плитки", - "WithSubfolders": "С подпапками" + "WithSubfolders": "С подпапками", + "DocumentEdited": "Невозможно выполнить действие, так как документ редактируется." } diff --git a/packages/client/src/components/panels/InvitePanel/StyledInvitePanel.js b/packages/client/src/components/panels/InvitePanel/StyledInvitePanel.js index 43c3d20e44..1b2e6e8743 100644 --- a/packages/client/src/components/panels/InvitePanel/StyledInvitePanel.js +++ b/packages/client/src/components/panels/InvitePanel/StyledInvitePanel.js @@ -91,6 +91,11 @@ const StyledRow = styled.div` font-size: 14px; line-height: 16px; } + + .invite-panel_access-selector { + margin-left: auto; + margin-right: 0; + } `; const StyledInviteInput = styled.div` diff --git a/packages/client/src/components/panels/InvitePanel/index.js b/packages/client/src/components/panels/InvitePanel/index.js index be060c7cf3..a708283be0 100644 --- a/packages/client/src/components/panels/InvitePanel/index.js +++ b/packages/client/src/components/panels/InvitePanel/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useRef } from "react"; import { observer, inject } from "mobx-react"; import { withTranslation } from "react-i18next"; import { isMobileOnly } from "react-device-detect"; @@ -52,6 +52,8 @@ const InvitePanel = ({ const [isLoading, setIsLoading] = useState(false); const [externalLinksVisible, setExternalLinksVisible] = useState(false); + const inputsRef = useRef(); + const onChangeExternalLinksVisible = (visible) => { setExternalLinksVisible(visible); }; @@ -236,6 +238,7 @@ const InvitePanel = ({ onClose={onClose} roomUsers={roomUsers} roomType={roomType} + inputsRef={inputsRef} /> {!!inviteItems.length && ( @@ -245,6 +248,7 @@ const InvitePanel = ({ setHasErrors={setHasErrors} roomType={roomType} externalLinksVisible={externalLinksVisible} + inputsRef={inputsRef} /> diff --git a/packages/client/src/components/panels/InvitePanel/sub-components/AccessSelector.js b/packages/client/src/components/panels/InvitePanel/sub-components/AccessSelector.js index 12d8fb29b8..6bbf6723cf 100644 --- a/packages/client/src/components/panels/InvitePanel/sub-components/AccessSelector.js +++ b/packages/client/src/components/panels/InvitePanel/sub-components/AccessSelector.js @@ -45,7 +45,7 @@ const AccessSelector = ({ : {}; return ( - + { const [inputValue, setInputValue] = useState(""); const [usersList, setUsersList] = useState([]); @@ -44,7 +45,6 @@ const InviteInput = ({ const [selectedAccess, setSelectedAccess] = useState(defaultAccess); - const inputsRef = useRef(); const searchRef = useRef(); const inRoom = (id) => { diff --git a/packages/client/src/components/panels/InvitePanel/sub-components/Item.js b/packages/client/src/components/panels/InvitePanel/sub-components/Item.js index ce86d44611..6a55134ca6 100644 --- a/packages/client/src/components/panels/InvitePanel/sub-components/Item.js +++ b/packages/client/src/components/panels/InvitePanel/sub-components/Item.js @@ -8,7 +8,6 @@ import { parseAddresses } from "@docspace/components/utils/email"; import { getAccessOptions } from "../utils"; import { - StyledComboBox, StyledEditInput, StyledEditButton, StyledCheckIcon, @@ -17,6 +16,7 @@ import { StyledDeleteIcon, } from "../StyledInvitePanel"; import { filterUserRoleOptions } from "SRC_DIR/helpers/utils"; +import AccessSelector from "./AccessSelector"; const Item = ({ t, @@ -27,6 +27,7 @@ const Item = ({ setHasErrors, roomType, isOwner, + inputsRef, }) => { const { avatar, displayName, email, id, errors, access } = item; @@ -37,7 +38,7 @@ const Item = ({ const [inputValue, setInputValue] = useState(name); const [parseErrors, setParseErrors] = useState(errors); - const accesses = getAccessOptions(t, roomType, true, false, isOwner); + const accesses = getAccessOptions(t, roomType, true, true, isOwner); const filteredAccesses = filterUserRoleOptions(accesses, item, true); @@ -130,20 +131,15 @@ const Item = ({ ) : ( - )} diff --git a/packages/client/src/components/panels/InvitePanel/sub-components/ItemsList.js b/packages/client/src/components/panels/InvitePanel/sub-components/ItemsList.js index d503563812..6d53591c1f 100644 --- a/packages/client/src/components/panels/InvitePanel/sub-components/ItemsList.js +++ b/packages/client/src/components/panels/InvitePanel/sub-components/ItemsList.js @@ -18,6 +18,7 @@ const Row = memo(({ data, index, style }) => { setHasErrors, roomType, isOwner, + inputsRef, } = data; if (inviteItems === undefined) return; @@ -35,6 +36,7 @@ const Row = memo(({ data, index, style }) => { setHasErrors={setHasErrors} roomType={roomType} isOwner={isOwner} + inputsRef={inputsRef} /> ); @@ -49,6 +51,7 @@ const ItemsList = ({ roomType, isOwner, externalLinksVisible, + inputsRef, }) => { const [bodyHeight, setBodyHeight] = useState(0); const [offsetTop, setOffsetTop] = useState(0); @@ -87,6 +90,7 @@ const ItemsList = ({ setHasErrors, roomType, isOwner, + inputsRef, t, }} outerElementType={CustomScrollbarsVirtualList} diff --git a/packages/client/src/components/panels/StyledPanels.js b/packages/client/src/components/panels/StyledPanels.js index dc6a42847f..cb4d064984 100644 --- a/packages/client/src/components/panels/StyledPanels.js +++ b/packages/client/src/components/panels/StyledPanels.js @@ -96,6 +96,7 @@ const StyledVersionHistoryPanel = styled.div` } .version-history-panel-header { + margin-bottom: 12px; height: 53px; margin-left: 0px; .version-history-panel-heading { @@ -113,11 +114,11 @@ const StyledVersionHistoryPanel = styled.div` box-sizing: border-box; .version-comment-wrapper { - margin-left: 79px; + margin-left: 85px; } .version_edit-comment { - padding-left: 2px; + padding-left: 7px; } } `; diff --git a/packages/client/src/components/panels/VersionHistoryPanel/index.js b/packages/client/src/components/panels/VersionHistoryPanel/index.js index a6fef1adc0..893c012bec 100644 --- a/packages/client/src/components/panels/VersionHistoryPanel/index.js +++ b/packages/client/src/components/panels/VersionHistoryPanel/index.js @@ -75,7 +75,7 @@ class PureVersionHistoryPanel extends React.Component { - + {showProgressBar && ( !props.isTabletView && "padding-right:16px"}; @@ -143,10 +143,9 @@ const StyledVersionRow = styled(Row)` white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - - @media ${tablet} { - margin-top: -1px; - } + } + .version-link-file:first-child { + margin-bottom: 4px; } .icon-link { @@ -164,7 +163,7 @@ const StyledVersionRow = styled(Row)` } .textarea-desktop { - margin: 9px 23px 1px -7px; + margin: 6px 31px 1px -7px; } .version_content-length { @@ -214,7 +213,7 @@ const StyledVersionRow = styled(Row)` .row_context-menu-wrapper { display: block; position: absolute; - right: 16px !important; + right: 13px !important; top: 6px; .expandButton { diff --git a/packages/client/src/pages/VersionHistory/Section/Body/VersionBadge.js b/packages/client/src/pages/VersionHistory/Section/Body/VersionBadge.js index 67a7698738..9dc47fdae1 100644 --- a/packages/client/src/pages/VersionHistory/Section/Body/VersionBadge.js +++ b/packages/client/src/pages/VersionHistory/Section/Body/VersionBadge.js @@ -16,7 +16,7 @@ const VersionBadge = ({ className={className} marginProp="0 8px" displayProp="flex" - isVersion={isVersion} + isVersion={true} theme={theme} index={index} {...rest} diff --git a/packages/client/src/pages/VersionHistory/Section/Body/VersionRow.js b/packages/client/src/pages/VersionHistory/Section/Body/VersionRow.js index 7b314cd8e9..83958037c1 100644 --- a/packages/client/src/pages/VersionHistory/Section/Body/VersionRow.js +++ b/packages/client/src/pages/VersionHistory/Section/Body/VersionRow.js @@ -1,3 +1,7 @@ +import AccessCommentReactSvgUrl from "PUBLIC_DIR/images/access.comment.react.svg?url"; +import RestoreAuthReactSvgUrl from "PUBLIC_DIR/images/restore.auth.react.svg?url"; +import { useHistory } from "react-router-dom"; +import DownloadReactSvgUrl from "PUBLIC_DIR/images/download.react.svg?url"; import React, { useState, useEffect } from "react"; import styled from "styled-components"; import Link from "@docspace/components/link"; @@ -10,7 +14,7 @@ import { withTranslation } from "react-i18next"; import { withRouter } from "react-router"; import VersionBadge from "./VersionBadge"; import { StyledVersionRow } from "./StyledVersionHistory"; -import ExternalLinkIcon from "PUBLIC_DIR/images/external.link.react.svg"; +import ExternalLinkIcon from "PUBLIC_DIR/images/external.link.react.svg?url"; import commonIconsStyles from "@docspace/components/utils/common-icons-style"; import { inject, observer } from "mobx-react"; import toastr from "@docspace/components/toast/toastr"; @@ -43,14 +47,18 @@ const VersionRow = (props) => { isEditing, theme, canChangeVersionFileHistory, + openUser, + onClose, + setIsVisible, } = props; const [showEditPanel, setShowEditPanel] = useState(false); const [commentValue, setCommentValue] = useState(info.comment); const [isSavingComment, setIsSavingComment] = useState(false); - const title = `${new Date(info.updated).toLocaleString( - culture - )} ${Encoder.htmlDecode(info.updatedBy?.displayName)}`; + const history = useHistory(); + + const versionDate = `${new Date(info.updated).toLocaleString(culture)}`; + const title = `${Encoder.htmlDecode(info.updatedBy?.displayName)}`; const linkStyles = { isHovered: true, type: "action" }; @@ -66,6 +74,12 @@ const VersionRow = (props) => { setCommentValue(value); }; + const onUserClick = () => { + onClose(true); + setIsVisible(true); + openUser(info?.updatedBy, history); + }; + const onSaveClick = () => { setIsSavingComment(true); updateCommentVersion(info.id, commentValue, info.version) @@ -98,27 +112,37 @@ const VersionRow = (props) => { }; const contextOptions = [ + { + key: "open", + icon: ExternalLinkIcon, + label: t("Files:Open"), + onClick: onOpenFile, + }, canChangeVersionFileHistory && { key: "edit", + icon: AccessCommentReactSvgUrl, label: t("EditComment"), onClick: onEditComment, }, index !== 0 && canChangeVersionFileHistory && { key: "restore", + icon: RestoreAuthReactSvgUrl, label: t("Common:Restore"), onClick: onRestoreClick, }, { key: "download", + icon: DownloadReactSvgUrl, label: `${t("Common:Download")} (${info.contentLength})`, onClick: onDownloadAction, }, ]; - const onClickProp = canChangeVersionFileHistory - ? { onClick: onVersionClick } - : {}; + // uncomment if we want to change versions again + // const onClickProp = canChangeVersionFileHistory + // ? { onClick: onVersionClick } + // : {}; useEffect(() => { const newRowHeight = document.getElementsByClassName( @@ -147,19 +171,35 @@ const VersionRow = (props) => { isVersion={isVersion} index={index} versionGroup={info.versionGroup} - {...onClickProp} + // {...onClickProp} t={t} /> - - {title} - + + {versionDate} + + + {title} + + {/* { {info.contentLength} */} - + <> {showEditPanel && ( <> @@ -191,7 +227,7 @@ const VersionRow = (props) => { )} - + {info.comment} @@ -232,6 +268,7 @@ const VersionRow = (props) => { export default inject(({ auth, versionHistoryStore, selectedFolderStore }) => { const { user } = auth.userStore; + const { openUser, setIsVisible } = auth.infoPanelStore; const { culture, isTabletView } = auth.settingsStore; const language = (user && user.cultureName) || culture || "en"; @@ -256,6 +293,8 @@ export default inject(({ auth, versionHistoryStore, selectedFolderStore }) => { updateCommentVersion, isEditing: isEdit, canChangeVersionFileHistory, + openUser, + setIsVisible, }; })( withRouter( diff --git a/packages/client/src/pages/VersionHistory/Section/Body/index.js b/packages/client/src/pages/VersionHistory/Section/Body/index.js index 22f56cbf4c..4536dc917f 100644 --- a/packages/client/src/pages/VersionHistory/Section/Body/index.js +++ b/packages/client/src/pages/VersionHistory/Section/Body/index.js @@ -64,7 +64,7 @@ class SectionBodyContent extends React.Component { this.setState((prevState) => ({ rowSizes: { ...prevState.rowSizes, - [i]: itemHeight + 24, //composed of itemHeight = clientHeight of div and padding-top = 12px and padding-bottom = 12px + [i]: itemHeight + 27, //composed of itemHeight = clientHeight of div and padding-top = 13px and padding-bottom = 12px }, })); }; @@ -74,7 +74,7 @@ class SectionBodyContent extends React.Component { }; renderRow = memo(({ index, style }) => { - const { versions, culture } = this.props; + const { versions, culture, onClose } = this.props; const prevVersion = versions[index > 0 ? index - 1 : index].versionGroup; let isVersion = true; @@ -85,6 +85,7 @@ class SectionBodyContent extends React.Component { return (
{ + toastr.error(t("Files:DocumentEdited")); + }; + onClickMute = (e, item, t) => { const data = (e.currentTarget && e.currentTarget.dataset) || e; const { action } = data; @@ -676,7 +680,7 @@ class ContextOptionsStore { return { pinOptions, muteOptions }; }; getFilesContextOptions = (item, t, isInfoPanel) => { - const { contextOptions } = item; + const { contextOptions, isEditing } = item; const { enablePlugins } = this.authStore.settingsStore; @@ -729,7 +733,10 @@ class ContextOptionsStore { key: "finalize-version", label: t("FinalizeVersion"), icon: HistoryFinalizedReactSvgUrl, - onClick: () => this.finalizeVersion(item.id, item.security), + onClick: () => + isEditing + ? this.onShowEditingToast(t) + : this.finalizeVersion(item.id, item.security), disabled: false, }, { @@ -750,7 +757,10 @@ class ContextOptionsStore { key: "finalize-version", label: t("FinalizeVersion"), icon: HistoryFinalizedReactSvgUrl, - onClick: () => this.finalizeVersion(item.id), + onClick: () => + isEditing + ? this.onShowEditingToast(t) + : this.finalizeVersion(item.id), disabled: false, }, { @@ -776,7 +786,9 @@ class ContextOptionsStore { key: "move-to", label: t("Common:MoveTo"), icon: MoveReactSvgUrl, - onClick: this.onMoveAction, + onClick: isEditing + ? () => this.onShowEditingToast(t) + : this.onMoveAction, disabled: false, }, { @@ -804,7 +816,9 @@ class ContextOptionsStore { key: "move-to", label: t("Common:MoveTo"), icon: MoveReactSvgUrl, - onClick: this.onMoveAction, + onClick: isEditing + ? () => this.onShowEditingToast(t) + : this.onMoveAction, disabled: false, }, { @@ -1099,7 +1113,8 @@ class ContextOptionsStore { ? t("Common:Disconnect") : t("Common:Delete"), icon: TrashReactSvgUrl, - onClick: () => this.onClickDelete(item, t), + onClick: () => + isEditing ? this.onShowEditingToast(t) : this.onClickDelete(item, t), disabled: false, }, ]; @@ -1144,7 +1159,7 @@ class ContextOptionsStore { getGroupContextOptions = (t) => { const { personal } = this.authStore.settingsStore; - const { selection } = this.filesStore; + const { selection, allFilesIsEditing } = this.filesStore; const { setDeleteDialogVisible } = this.dialogsStore; const { isRecycleBinFolder, @@ -1152,12 +1167,7 @@ class ContextOptionsStore { isArchiveFolder, } = this.treeFoldersStore; - const { - pinRooms, - unpinRooms, - - deleteRooms, - } = this.filesActionsStore; + const { pinRooms, unpinRooms, deleteRooms } = this.filesActionsStore; if (isRoomsFolder || isArchiveFolder) { const isPinOption = selection.filter((item) => !item.pinned).length > 0; @@ -1339,7 +1349,9 @@ class ContextOptionsStore { key: "move-to", label: t("Common:MoveTo"), icon: MoveReactSvgUrl, - onClick: this.onMoveAction, + onClick: allFilesIsEditing + ? () => this.onShowEditingToast(t) + : this.onMoveAction, disabled: isRecycleBinFolder || !moveItems, }, { @@ -1365,23 +1377,25 @@ class ContextOptionsStore { key: "delete", label: t("Common:Delete"), icon: TrashReactSvgUrl, - onClick: () => { - if (this.settingsStore.confirmDelete) { - setDeleteDialogVisible(true); - } else { - const translations = { - deleteOperation: t("Translations:DeleteOperation"), - deleteFromTrash: t("Translations:DeleteFromTrash"), - deleteSelectedElem: t("Translations:DeleteSelectedElem"), - FileRemoved: t("Files:FileRemoved"), - FolderRemoved: t("Files:FolderRemoved"), - }; + onClick: allFilesIsEditing + ? () => this.onShowEditingToast(t) + : () => { + if (this.settingsStore.confirmDelete) { + setDeleteDialogVisible(true); + } else { + const translations = { + deleteOperation: t("Translations:DeleteOperation"), + deleteFromTrash: t("Translations:DeleteFromTrash"), + deleteSelectedElem: t("Translations:DeleteSelectedElem"), + FileRemoved: t("Files:FileRemoved"), + FolderRemoved: t("Files:FolderRemoved"), + }; - this.filesActionsStore - .deleteAction(translations) - .catch((err) => toastr.error(err)); - } - }, + this.filesActionsStore + .deleteAction(translations) + .catch((err) => toastr.error(err)); + } + }, disabled: !deleteItems || isRootThirdPartyFolder, }, ]; diff --git a/packages/client/src/store/FilesStore.js b/packages/client/src/store/FilesStore.js index 7e2c470926..3a4bbd8f7f 100644 --- a/packages/client/src/store/FilesStore.js +++ b/packages/client/src/store/FilesStore.js @@ -28,6 +28,7 @@ import { isDesktop } from "@docspace/components/utils/device"; import { getContextMenuKeysByType } from "SRC_DIR/helpers/plugins"; import { PluginContextMenuItemType } from "SRC_DIR/helpers/plugins/constants"; import debounce from "lodash.debounce"; +import Queue from "queue-promise"; const { FilesFilter, RoomsFilter } = api; const storageViewAs = localStorage.getItem("viewAs"); @@ -129,6 +130,11 @@ class FilesStore { highlightFile = {}; thumbnails = new Set(); movingInProgress = false; + createNewFilesQueue = new Queue({ + concurrent: 5, + interval: 500, + start: true, + }); constructor( authStore, @@ -272,8 +278,43 @@ class FilesStore { this.createThumbnail(this.files[foundIndex]); }); + + this.createNewFilesQueue.on("resolve", this.onResolveNewFile); } + onResolveNewFile = (fileInfo) => { + if (!fileInfo) return; + + //console.log("onResolveNewFiles", { fileInfo }); + + if (this.files.findIndex((x) => x.id === fileInfo.id) > -1) return; + + console.log("[WS] create new file", { fileInfo }); + + const newFiles = [fileInfo, ...this.files]; + + if ( + newFiles.length > this.filter.pageCount && + this.authStore.settingsStore.withPaging + ) { + newFiles.pop(); // Remove last + } + + const newFilter = this.filter; + newFilter.total += 1; + + runInAction(() => { + this.setFilter(newFilter); + this.setFiles(newFiles); + }); + + this.debouncefetchTreeFolders(); + }; + + debouncefetchTreeFolders = debounce(() => { + this.treeFoldersStore.fetchTreeFolders(); + }, 1000); + debounceRemoveFiles = debounce(() => { this.removeFiles(this.tempActionFilesIds); }, 1000); @@ -296,34 +337,35 @@ class FilesStore { //To update a file version if (foundIndex > -1 && !this.authStore.settingsStore.withPaging) { - this.getFileInfo(file.id); + if ( + this.files[foundIndex].version !== file.version || + this.files[foundIndex].versionGroup !== file.versionGroup + ) { + this.files[foundIndex].version = file.version; + this.files[foundIndex].versionGroup = file.versionGroup; + } this.checkSelection(file); } if (foundIndex > -1) return; - const fileInfo = await api.files.getFileInfo(file.id); + setTimeout(() => { + const foundIndex = this.files.findIndex((x) => x.id === file.id); + if (foundIndex > -1) { + //console.log("Skip in timeout"); + return null; + } - if (this.files.findIndex((x) => x.id === opt?.id) > -1) return; - console.log("[WS] create new file", fileInfo.id, fileInfo.title); + this.createNewFilesQueue.enqueue(() => { + const foundIndex = this.files.findIndex((x) => x.id === file.id); + if (foundIndex > -1) { + //console.log("Skip in queue"); + return null; + } - const newFiles = [fileInfo, ...this.files]; - - if ( - newFiles.length > this.filter.pageCount && - this.authStore.settingsStore.withPaging - ) { - newFiles.pop(); // Remove last - } - - const newFilter = this.filter; - newFilter.total += 1; - - runInAction(() => { - this.setFilter(newFilter); - this.setFiles(newFiles); - this.treeFoldersStore.fetchTreeFolders(); - }); + return api.files.getFileInfo(file.id); + }); + }, 300); } else if (opt?.type === "folder" && opt?.id) { const foundIndex = this.folders.findIndex((x) => x.id === opt?.id); @@ -1550,10 +1592,7 @@ class FilesStore { const canConvert = this.filesSettingsStore.extsConvertible[item.fileExst]; const isEncrypted = item.encrypted; const isDocuSign = false; //TODO: need this prop; - const isEditing = - (item.fileStatus & FileStatus.IsEditing) === FileStatus.IsEditing; - // const isFileOwner = - // item.createdBy?.id === this.authStore.userStore.user?.id; + const isEditing = false; // (item.fileStatus & FileStatus.IsEditing) === FileStatus.IsEditing; const { isRecycleBinFolder, isMy, isArchiveFolder } = this.treeFoldersStore; @@ -1614,7 +1653,7 @@ class FilesStore { "send-by-email", "docu-sign", "version", //category - "finalize-version", + // "finalize-version", "show-version-history", "show-info", "block-unblock-version", //need split diff --git a/packages/client/src/store/UploadDataStore.js b/packages/client/src/store/UploadDataStore.js index 21123866ce..3cd0fc8403 100644 --- a/packages/client/src/store/UploadDataStore.js +++ b/packages/client/src/store/UploadDataStore.js @@ -906,7 +906,7 @@ class UploadDataStore { return Promise.reject(res.data.message); } - const { uploaded, id: fileId } = res.data.data; + const { uploaded, id: fileId, file: fileInfo } = res.data.data; let uploadedSize, newPercent; @@ -947,7 +947,6 @@ class UploadDataStore { }); if (uploaded) { - const fileInfo = await getFileInfo(fileId); runInAction(() => { this.files[indexOfFile].action = "uploaded"; this.files[indexOfFile].fileId = fileId; diff --git a/packages/common/components/Article/sub-components/article-apps.js b/packages/common/components/Article/sub-components/article-apps.js index eaac6f65a0..47fde040e0 100644 --- a/packages/common/components/Article/sub-components/article-apps.js +++ b/packages/common/components/Article/sub-components/article-apps.js @@ -96,12 +96,13 @@ const ArticleApps = React.memo(({ theme, showText }) => { title={t("Translations:MobileAndroid")} /> window.open(iosLink)} iconName={IOSReactSvgUrl} iconHoverName={IOSHoverReactSvgUrl} size="32" isFill={false} title={t("Translations:MobileIos")} + onMouseDown={() => window.open(iosLink)} + isClickable={true} />
diff --git a/packages/common/components/ColorTheme/styled/versionBadge.js b/packages/common/components/ColorTheme/styled/versionBadge.js index 4adf8265e6..cf7aa5c87c 100644 --- a/packages/common/components/ColorTheme/styled/versionBadge.js +++ b/packages/common/components/ColorTheme/styled/versionBadge.js @@ -8,15 +8,11 @@ const getDefaultStyles = ({ $currentColorScheme, $isVersion, theme, index }) => path { fill: ${!$isVersion ? theme.filesVersionHistory.badge.defaultFill - : index === 0 - ? theme.filesVersionHistory.badge.fill - : $currentColorScheme.main.accent}; + : theme.filesVersionHistory.badge.fill}; stroke: ${!$isVersion ? theme.filesVersionHistory.badge.stroke - : index === 0 - ? theme.filesVersionHistory.badge.fill - : $currentColorScheme.main.accent}; + : theme.filesVersionHistory.badge.fill}; } } diff --git a/packages/common/components/MediaViewer/sub-components/PlayerTimeline/PlayerTimeline.styled.ts b/packages/common/components/MediaViewer/sub-components/PlayerTimeline/PlayerTimeline.styled.ts index 5547c88f00..d0126a9c20 100644 --- a/packages/common/components/MediaViewer/sub-components/PlayerTimeline/PlayerTimeline.styled.ts +++ b/packages/common/components/MediaViewer/sub-components/PlayerTimeline/PlayerTimeline.styled.ts @@ -62,7 +62,7 @@ export const PlayerTimelineWrapper = styled.div` display: flex; align-items: center; - margin-top: 92px; + margin-top: 12px; height: 4px; width: 100%; diff --git a/packages/common/components/MediaViewer/sub-components/ViewerPlayer/ViewerPlayer.styled.ts b/packages/common/components/MediaViewer/sub-components/ViewerPlayer/ViewerPlayer.styled.ts index 26fbf22bf3..64511eba98 100644 --- a/packages/common/components/MediaViewer/sub-components/ViewerPlayer/ViewerPlayer.styled.ts +++ b/packages/common/components/MediaViewer/sub-components/ViewerPlayer/ViewerPlayer.styled.ts @@ -83,6 +83,12 @@ export const ControlContainer = styled.div` export const PlayerControlsWrapper = styled.div` padding: 0 30px; width: 100%; + margin-top: 80px; + + ${isMobile && + css` + margin-top: 0px; + `} ${isMobileOnly && css` diff --git a/packages/common/components/MediaViewer/sub-components/ViewerPlayer/index.tsx b/packages/common/components/MediaViewer/sub-components/ViewerPlayer/index.tsx index 0434155ec0..2649d7e409 100644 --- a/packages/common/components/MediaViewer/sub-components/ViewerPlayer/index.tsx +++ b/packages/common/components/MediaViewer/sub-components/ViewerPlayer/index.tsx @@ -533,6 +533,14 @@ function ViewerPlayer({ const onTouchStart = () => { if (isPlaying && isVideo) restartToolbarVisibleTimer(); }; + + const stopPropagation = useCallback( + (event: React.MouseEvent) => { + event?.stopPropagation(); + }, + [] + ); + const onTouchMove = () => { if (isPlaying && isVideo) restartToolbarVisibleTimer(); }; @@ -599,8 +607,9 @@ function ViewerPlayer({ $isShow={panelVisible && !isLoading} onTouchStart={onTouchStart} onTouchMove={onTouchMove} + onClick={handleClickVideo} > - + _logger; public ChunkedUploaderHandlerService( @@ -66,7 +65,8 @@ public class ChunkedUploaderHandlerService InstanceCrypto instanceCrypto, ChunkedUploadSessionHolder chunkedUploadSessionHolder, ChunkedUploadSessionHelper chunkedUploadSessionHelper, - SocketManager socketManager) + SocketManager socketManager, + FileDtoHelper filesWrapperHelper) { _tenantManager = tenantManager; _fileUploader = fileUploader; @@ -78,6 +78,7 @@ public class ChunkedUploaderHandlerService _chunkedUploadSessionHolder = chunkedUploadSessionHolder; _chunkedUploadSessionHelper = chunkedUploadSessionHelper; _socketManager = socketManager; + _filesWrapperHelper = filesWrapperHelper; _logger = logger; } @@ -139,7 +140,7 @@ public class ChunkedUploaderHandlerService if (resumedSession.BytesUploaded == resumedSession.BytesTotal) { - await WriteSuccess(context, ToResponseObject(resumedSession.File), (int)HttpStatusCode.Created); + await WriteSuccess(context, await ToResponseObject(resumedSession.File), (int)HttpStatusCode.Created); _ = _filesMessageService.Send(resumedSession.File, MessageAction.FileUploaded, resumedSession.File.Title); await _socketManager.CreateFileAsync(resumedSession.File); @@ -218,10 +219,10 @@ public class ChunkedUploaderHandlerService context.Response.StatusCode = statusCode; context.Response.ContentType = "application/json"; - return context.Response.WriteAsync(JsonConvert.SerializeObject(new { success, data, message })); + return context.Response.WriteAsync(JsonSerializer.Serialize(new { success, data, message }, SocketManager.GetSerializerSettings())); } - private static object ToResponseObject(File file) + private async Task ToResponseObject(File file) { return new { @@ -230,7 +231,8 @@ public class ChunkedUploaderHandlerService version = file.Version, title = file.Title, provider_key = file.ProviderKey, - uploaded = true + uploaded = true, + file = await _filesWrapperHelper.GetAsync(file) }; } } diff --git a/products/ASC.Files/Core/Utils/SocketManager.cs b/products/ASC.Files/Core/Utils/SocketManager.cs index 2dc0843395..171d1a6399 100644 --- a/products/ASC.Files/Core/Utils/SocketManager.cs +++ b/products/ASC.Files/Core/Utils/SocketManager.cs @@ -150,7 +150,7 @@ public class SocketManager : SocketServiceClient return JsonSerializer.Serialize(await _folderDtoHelper.GetAsync(folder), GetSerializerSettings()); ; } - private JsonSerializerOptions GetSerializerSettings() + public static JsonSerializerOptions GetSerializerSettings() { var serializerSettings = new JsonSerializerOptions() { diff --git a/yarn.lock b/yarn.lock index 5c3ad3d5ee..b9a55d55be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3031,6 +3031,7 @@ __metadata: html-webpack-plugin: 5.3.2 json-loader: ^0.5.7 playwright: ^1.18.1 + queue-promise: 2.2.1 react-avatar-editor: ^13.0.0 react-colorful: ^5.5.1 react-hotkeys-hook: ^3.4.4 @@ -20810,6 +20811,13 @@ __metadata: languageName: node linkType: hard +"queue-promise@npm:2.2.1": + version: 2.2.1 + resolution: "queue-promise@npm:2.2.1" + checksum: 139c1844225580545a94c5a234fcde33e47941f473525bb0df3dacbdd55724993754f048c25c3a8b98961cd84221913b633397383ef8bc92476237d0dca6d721 + languageName: node + linkType: hard + "raf@npm:^3.1.0, raf@npm:^3.4.1": version: 3.4.1 resolution: "raf@npm:3.4.1"