Merge branch 'release/1.0.0' into bugfix/filterlist-extra-items

This commit is contained in:
Dmitry Sychugov 2021-08-04 14:33:06 +05:00
commit 390430c3ee
12 changed files with 389 additions and 300 deletions

View File

@ -122,10 +122,6 @@ namespace ASC.Data.Storage.DiscStorage
path += ".gz";
encoding = "gzip";
}
using (var stream = storage.GetReadStream(_domain, path))
{
await stream.CopyToAsync(context.Response.Body);
}
var headersToCopy = new List<string> { "Content-Disposition", "Cache-Control", "Content-Encoding", "Content-Language", "Content-Type", "Expires" };
foreach (var h in headers)
@ -145,7 +141,15 @@ namespace ASC.Data.Storage.DiscStorage
//}
if (encoding != null)
context.Response.Headers["Content-Encoding"] = encoding;
context.Response.Headers["Content-Encoding"] = encoding;
using (var stream = storage.GetReadStream(_domain, path))
{
await stream.CopyToAsync(context.Response.Body);
}
await context.Response.Body.FlushAsync();
await context.Response.CompleteAsync();
string GetRouteValue(string name)
{

View File

@ -32,8 +32,6 @@ class SnackBar extends React.Component {
const bar = document.querySelector(`#${window.snackbar.parentElementId}`);
bar.remove();
//ReactDOM.unmountComponentAtNode(window.snackbar.parentElementId);
} else {
console.error("Not found snackbar");
}
}

View File

@ -52,7 +52,7 @@ namespace ASC.Web.Files.Core.Compress
/// <param name="title">File name with extension, this name will have the file in the archive</param>
public void CreateEntry(string title)
{
zipEntry = new ZipEntry(title);
zipEntry = new ZipEntry(title) { IsUnicodeText = true };
}
/// <summary>

View File

@ -28,7 +28,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using ASC.Common;
@ -49,9 +48,6 @@ using ASC.Web.Studio.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
using SharpCompress.Common;
using SharpCompress.Writers.Zip;
namespace ASC.Web.Files.Services.WCFService.FileOperations
{
internal class FileDownloadOperationData<T> : FileOperationData<T>
@ -89,21 +85,23 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
using var scope = ThirdPartyOperation.CreateScope();
var scopeClass = scope.ServiceProvider.GetService<FileDownloadOperationScope>();
var (zip, globalStore, filesLinkUtility, _, _, _) = scopeClass;
using var stream = TempStream.Create();
var writerOptions = new ZipWriterOptions(CompressionType.Deflate);
writerOptions.ArchiveEncoding.Default = Encoding.UTF8;
writerOptions.DeflateCompressionLevel = SharpCompress.Compressors.Deflate.CompressionLevel.Level3;
zip.SetStream(stream);
(ThirdPartyOperation as FileDownloadOperation<string>).CompressToZip(zip, stream, scope);
(DaoOperation as FileDownloadOperation<int>).CompressToZip(zip, stream, scope);
var (globalStore, filesLinkUtility, _, _, _) = scopeClass;
var stream = TempStream.Create();
(ThirdPartyOperation as FileDownloadOperation<string>).CompressToZip(stream, scope);
(DaoOperation as FileDownloadOperation<int>).CompressToZip(stream, scope);
if (stream != null)
{
var archiveExtension = "";
using(var zip = scope.ServiceProvider.GetService<CompressToArchive>())
{
archiveExtension = zip.ArchiveExtension;
}
stream.Position = 0;
string fileName = FileConstant.DownloadTitle + zip.ArchiveExtension;
string fileName = FileConstant.DownloadTitle + archiveExtension;
var store = globalStore.GetStore();
var path = string.Format(@"{0}\{1}", ((IAccount)Thread.CurrentPrincipal.Identity).ID, fileName);
@ -118,7 +116,7 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
stream,
MimeMapping.GetMimeMapping(path),
"attachment; filename=\"" + fileName + "\"");
Result = string.Format("{0}?{1}=bulk&ext={2}", filesLinkUtility.FileHandlerPath, FilesLinkUtility.Action, zip.ArchiveExtension);
Result = string.Format("{0}?{1}=bulk&ext={2}", filesLinkUtility.FileHandlerPath, FilesLinkUtility.Action, archiveExtension);
}
FillDistributedTask();
@ -239,114 +237,121 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
return entriesPathId;
}
internal void CompressToZip(ICompress compressTo, Stream stream, IServiceScope scope)
internal void CompressToZip(Stream stream, IServiceScope scope)
{
if (entriesPathId == null) return;
var scopeClass = scope.ServiceProvider.GetService<FileDownloadOperationScope>();
var (_, _, _, _, fileConverter, filesMessageService) = scopeClass;
var (_, _, _, fileConverter, filesMessageService) = scopeClass;
var FileDao = scope.ServiceProvider.GetService<IFileDao<T>>();
foreach (var path in entriesPathId.AllKeys)
using (var compressTo = scope.ServiceProvider.GetService<CompressToArchive>())
{
var counter = 0;
foreach (var entryId in entriesPathId[path])
compressTo.SetStream(stream);
foreach (var path in entriesPathId.AllKeys)
{
if (CancellationToken.IsCancellationRequested)
var counter = 0;
foreach (var entryId in entriesPathId[path])
{
compressTo.Dispose();
stream.Dispose();
CancellationToken.ThrowIfCancellationRequested();
}
var newtitle = path;
File<T> file = null;
var convertToExt = string.Empty;
if (!Equals(entryId, default(T)))
{
FileDao.InvalidateCache(entryId);
file = FileDao.GetFile(entryId);
if (file == null)
if (CancellationToken.IsCancellationRequested)
{
Error = FilesCommonResource.ErrorMassage_FileNotFound;
continue;
compressTo.Dispose();
stream.Dispose();
CancellationToken.ThrowIfCancellationRequested();
}
if (files.ContainsKey(file.ID))
{
convertToExt = files[file.ID];
if (!string.IsNullOrEmpty(convertToExt))
{
newtitle = FileUtility.ReplaceFileExtension(path, convertToExt);
}
}
}
var newtitle = path;
if (0 < counter)
{
var suffix = " (" + counter + ")";
File<T> file = null;
var convertToExt = string.Empty;
if (!Equals(entryId, default(T)))
{
newtitle = 0 < newtitle.IndexOf('.') ? newtitle.Insert(newtitle.LastIndexOf('.'), suffix) : newtitle + suffix;
}
else
{
break;
}
}
FileDao.InvalidateCache(entryId);
file = FileDao.GetFile(entryId);
compressTo.CreateEntry(newtitle);
if (!Equals(entryId, default(T)) && file != null)
{
try
{
if (fileConverter.EnableConvert(file, convertToExt))
if (file == null)
{
//Take from converter
using (var readStream = fileConverter.Exec(file, convertToExt))
{
compressTo.PutStream(readStream);
Error = FilesCommonResource.ErrorMassage_FileNotFound;
continue;
}
if (!string.IsNullOrEmpty(convertToExt))
if (files.ContainsKey(file.ID))
{
convertToExt = files[file.ID];
if (!string.IsNullOrEmpty(convertToExt))
{
newtitle = FileUtility.ReplaceFileExtension(path, convertToExt);
}
}
}
if (0 < counter)
{
var suffix = " (" + counter + ")";
if (!Equals(entryId, default(T)))
{
newtitle = 0 < newtitle.IndexOf('.') ? newtitle.Insert(newtitle.LastIndexOf('.'), suffix) : newtitle + suffix;
}
else
{
break;
}
}
compressTo.CreateEntry(newtitle);
if (!Equals(entryId, default(T)) && file != null)
{
try
{
if (fileConverter.EnableConvert(file, convertToExt))
{
//Take from converter
using (var readStream = fileConverter.Exec(file, convertToExt))
{
filesMessageService.Send(file, headers, MessageAction.FileDownloadedAs, file.Title, convertToExt);
compressTo.PutStream(readStream);
if (!string.IsNullOrEmpty(convertToExt))
{
filesMessageService.Send(file, headers, MessageAction.FileDownloadedAs, file.Title, convertToExt);
}
else
{
filesMessageService.Send(file, headers, MessageAction.FileDownloaded, file.Title);
}
}
else
}
else
{
using (var readStream = FileDao.GetFileStream(file))
{
compressTo.PutStream(readStream);
filesMessageService.Send(file, headers, MessageAction.FileDownloaded, file.Title);
}
}
}
else
catch (Exception ex)
{
using (var readStream = FileDao.GetFileStream(file))
{
compressTo.PutStream(readStream);
filesMessageService.Send(file, headers, MessageAction.FileDownloaded, file.Title);
}
Error = ex.Message;
Logger.Error(Error, ex);
}
}
catch (Exception ex)
else
{
Error = ex.Message;
Logger.Error(Error, ex);
compressTo.PutNextEntry();
}
compressTo.CloseEntry();
counter++;
}
else
{
compressTo.PutNextEntry();
}
compressTo.CloseEntry();
counter++;
}
ProgressStep();
}
}
ProgressStep();
}
}
private void ReplaceLongPath(ItemNameValueCollection<T> entriesPathId)
@ -429,27 +434,23 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
private SetupInfo SetupInfo { get; }
private FileConverter FileConverter { get; }
private FilesMessageService FilesMessageService { get; }
private CompressToArchive CompressToArchive { get; }
public FileDownloadOperationScope(
GlobalStore globalStore,
FilesLinkUtility filesLinkUtility,
SetupInfo setupInfo,
FileConverter fileConverter,
FilesMessageService filesMessageService,
CompressToArchive compressToArchive)
FilesMessageService filesMessageService)
{
GlobalStore = globalStore;
FilesLinkUtility = filesLinkUtility;
SetupInfo = setupInfo;
FileConverter = fileConverter;
FilesMessageService = filesMessageService;
CompressToArchive = compressToArchive;
}
public void Deconstruct(out CompressToArchive compressToArchive, out GlobalStore globalStore, out FilesLinkUtility filesLinkUtility, out SetupInfo setupInfo, out FileConverter fileConverter, out FilesMessageService filesMessageService)
{
compressToArchive = CompressToArchive;
public void Deconstruct(out GlobalStore globalStore, out FilesLinkUtility filesLinkUtility, out SetupInfo setupInfo, out FileConverter fileConverter, out FilesMessageService filesMessageService)
{
globalStore = GlobalStore;
filesLinkUtility = FilesLinkUtility;
setupInfo = SetupInfo;

View File

@ -34,7 +34,8 @@ using ASC.Common;
using ASC.Common.Threading;
using ASC.Core.Tenants;
using ASC.Files.Core.Resources;
using ASC.Web.Files.Core.Compress;
using Microsoft.Extensions.Primitives;
namespace ASC.Web.Files.Services.WCFService.FileOperations
@ -181,6 +182,7 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
services.TryAdd<FileMoveCopyOperationScope>();
services.TryAdd<FileOperationScope>();
services.TryAdd<FileDownloadOperationScope>();
services.TryAdd<CompressToArchive>();
services.AddDistributedTaskQueueService<FileOperation>(10);
}
}

View File

@ -1,9 +1,9 @@
{
"AboutCompanyAddressTitle": "address",
"AboutCompanyEmailTitle": "email",
"AboutCompanyLicensor": "Copyright",
"AboutCompanyTelTitle": "tel.",
"AllRightsReservedCustomMode": "<0>Ascensio System SIA.</0> <1>All rights reserved.</1>",
"LicensedUnder": "This software is licensed under: <1>{{license}}</1>",
"SourceCode": "Source code is available on"
}
"AboutHeader": "About this program",
"DocumentManagement": "Document management",
"OnlineEditors": "Online editors",
"SoftwareLicense": "Software license",
"AboutCompanyAddressTitle": "Address",
"AboutCompanyEmailTitle": "Email",
"AboutCompanyTelTitle": "Phone"
}

View File

@ -1,9 +1,9 @@
{
"AboutCompanyAddressTitle": "адрес",
"AboutCompanyEmailTitle": "email",
"AboutCompanyLicensor": "АВТОРСКИЕ ПРАВА",
"AboutCompanyTelTitle": "тел.",
"AllRightsReservedCustomMode": "<0>Ascensio System SIA.</0> <1>All rights reserved.</1>",
"LicensedUnder": "Программа распространяется под лицензией: {{license}}",
"SourceCode": "Исходный код программы доступен по cсылке"
}
"AboutHeader": "Об этой программе",
"DocumentManagement": "Управление документами",
"OnlineEditors": "Онлайн редакторы",
"SoftwareLicense": "Лицензия на ПО",
"AboutCompanyAddressTitle": "Адрес",
"AboutCompanyEmailTitle": "Email",
"AboutCompanyTelTitle": "Телефон"
}

View File

@ -10,6 +10,8 @@ import { inject, observer } from "mobx-react";
import { withRouter } from "react-router";
import { AppServerConfig } from "@appserver/common/constants";
import config from "../../../../package.json";
import { isDesktop } from "react-device-detect";
import AboutDialog from "../../pages/About/AboutDialog";
const { proxyURL } = AppServerConfig;
const homepage = config.homepage;
@ -61,8 +63,10 @@ const HeaderNav = ({
isAuthenticated,
peopleAvailable,
isPersonal,
versionAppServer,
}) => {
const { t } = useTranslation(["NavMenu", "Common"]);
const { t } = useTranslation(["NavMenu", "Common", "About"]);
const [visibleDialog, setVisibleDialog] = useState(false);
const onProfileClick = useCallback(() => {
peopleAvailable
@ -70,7 +74,15 @@ const HeaderNav = ({
: window.open(PROFILE_MY_URL, "_blank");
}, []);
const onAboutClick = useCallback(() => history.push(ABOUT_URL), []);
const onAboutClick = useCallback(() => {
if (isDesktop) {
setVisibleDialog(true);
} else {
history.push(ABOUT_URL);
}
}, []);
const onCloseDialog = () => setVisibleDialog(false);
const onSwitchToDesktopClick = useCallback(() => {
deleteCookie("desktop_view");
@ -142,6 +154,14 @@ const HeaderNav = ({
) : (
<></>
)}
<AboutDialog
t={t}
visible={visibleDialog}
onClose={onCloseDialog}
personal={isPersonal}
versionAppServer={versionAppServer}
/>
</StyledNav>
);
};
@ -167,7 +187,11 @@ export default withRouter(
language,
logout,
} = auth;
const { defaultPage, personal: isPersonal } = settingsStore;
const {
defaultPage,
personal: isPersonal,
version: versionAppServer,
} = settingsStore;
const { user } = userStore;
const modules = auth.availableModules;
return {
@ -180,6 +204,7 @@ export default withRouter(
modules,
logout,
peopleAvailable: modules.some((m) => m.appName === "people"),
versionAppServer,
};
})(observer(HeaderNav))
);

View File

@ -0,0 +1,115 @@
import React from "react";
import Text from "@appserver/components/text";
import Link from "@appserver/components/link";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { ReactSVG } from "react-svg";
const AboutBody = styled.div`
width: 100%;
.avatar {
margin-top: 32px;
margin-bottom: 16px;
}
.row {
display: flex;
flex-direction: row;
}
.copyright {
margin-top: 16px;
}
`;
const AboutContent = ({ personal, versionAppServer }) => {
const { t } = useTranslation("About");
const versionEditor = "6.3.1";
const license = "AGPL-3.0";
const link = "https://github.com/ONLYOFFICE";
const phone = "+371 660-16425";
const email = "support@onlyoffice.com";
const address =
"20A-12 Ernesta Birznieka-Upisha street, Riga, Latvia, EU, LV-1050";
return (
<AboutBody>
<div className="avatar">
{personal ? (
<ReactSVG src="images/logo_personal_about.svg" />
) : (
<img src="images/dark_general.png" alt="Logo" />
)}
</div>
<div className="row">
<Text fontSize="13px">{t("DocumentManagement")}:</Text>
<Link
color="#2DA7DB"
fontSize="13px"
fontWeight="600"
href={link}
target="_blank"
>
ONLYOFFICE App Server
</Link>
<Text fontSize="13px" fontWeight="600">
v.{versionAppServer}
</Text>
</div>
<div className="row">
<Text fontSize="13px">{t("OnlineEditors")}:</Text>
<Link
color="#2DA7DB"
fontSize="13px"
fontWeight="600"
href={link}
target="_blank"
>
ONLYOFFICE Docs
</Link>
<Text fontSize="13px" fontWeight="600">
v.{versionEditor}
</Text>
</div>
<div className="row">
<Text fontSize="13px">{t("SoftwareLicense")}: </Text>
<Text fontSize="13px" fontWeight="600">
{license}
</Text>
</div>
<Text className="copyright" fontSize="14px" fontWeight="600">
© Ascensio System SIA
</Text>
<div className="row">
<Text fontSize="13px">
{t("AboutCompanyAddressTitle")}: {address}
</Text>
</div>
<div className="row">
<Text fontSize="13px">
{t("AboutCompanyTelTitle")}: {phone}
</Text>
</div>
<div className="row">
<Text fontSize="13px">{t("AboutCompanyEmailTitle")}:</Text>
<Link
color="#2DA7DB"
fontSize="13px"
fontWeight="600"
href={`mailto:${email}`}
>
{email}
</Link>
</div>
</AboutBody>
);
};
export default AboutContent;

View File

@ -0,0 +1,42 @@
import React from "react";
import PropTypes from "prop-types";
import ModalDialog from "@appserver/components/modal-dialog";
import ModalDialogContainer from "./ModalDialogContainer";
import { I18nextProvider, useTranslation } from "react-i18next";
import AboutContent from "./AboutContent";
import i18n from "./i18n";
const AboutDialog = (props) => {
const { visible, onClose, personal, versionAppServer } = props;
const { t, ready } = useTranslation(["About", "Common"]);
return (
<ModalDialogContainer
isLoading={!ready}
visible={visible}
onClose={onClose}
>
<ModalDialog.Header>{t("AboutHeader")}</ModalDialog.Header>
<ModalDialog.Body>
<AboutContent personal={personal} versionAppServer={versionAppServer} />
</ModalDialog.Body>
</ModalDialogContainer>
);
};
AboutDialog.propTypes = {
visible: PropTypes.bool,
onClose: PropTypes.func,
personal: PropTypes.bool,
versionAppServer: PropTypes.string,
};
const AboutDialogWrapper = (props) => {
return (
<I18nextProvider i18n={i18n}>
<AboutDialog {...props} />
</I18nextProvider>
);
};
export default AboutDialogWrapper;

View File

@ -0,0 +1,67 @@
import styled from "styled-components";
import { tablet } from "@appserver/components/utils/device";
import ModalDialog from "@appserver/components/modal-dialog";
const ModalDialogContainer = styled(ModalDialog)`
.invite-link-dialog-wrapper {
display: flex;
@media ${tablet} {
display: grid;
grid-gap: 8px;
grid-template-columns: auto;
}
}
.text-dialog {
margin: 16px 0;
}
.input-dialog {
margin-top: 16px;
}
.button-dialog {
margin-left: 8px;
}
.warning-text {
margin: 20px 0;
}
.textarea-dialog {
margin-top: 12px;
}
.link-dialog {
transition: opacity 0.2s;
margin-right: 12px;
opacity: ${(props) => (props.ChangeTextAnim ? 0 : 1)};
}
.error-label {
position: absolute;
max-width: 100%;
}
.field-body {
position: relative;
}
.toggle-content-dialog {
.heading-toggle-content {
font-size: 16px;
}
.modal-dialog-content {
padding: 8px 16px;
border: 1px solid lightgray;
.modal-dialog-checkbox:not(:last-child) {
padding-bottom: 4px;
}
}
}
`;
export default ModalDialogContainer;

View File

@ -1,203 +1,38 @@
import React, { useEffect } from "react";
import Text from "@appserver/components/text";
import Link from "@appserver/components/link";
import PageLayout from "@appserver/common/components/PageLayout";
import { I18nextProvider, Trans, withTranslation } from "react-i18next";
import styled from "styled-components";
import { isMobile } from "react-device-detect";
import { isMobileOnly } from "react-device-detect";
import { setDocumentTitle } from "../../../helpers/utils";
import i18n from "./i18n";
import withLoader from "../Confirm/withLoader";
import { inject, observer } from "mobx-react";
import { ReactSVG } from "react-svg";
import AboutContent from "./AboutContent";
const BodyStyle = styled.div`
margin-top: ${isMobile ? "80px" : "24px"};
.avatar {
text-align: center;
margin: 0px;
}
.text-about {
margin-top: 4px;
}
.text-license {
margin-top: 20px;
}
.text_style {
text-align: center;
}
.logo-img {
text-align: center;
max-width: 216px;
max-height: 35px;
}
.hidden-text {
height: 0;
visibility: hidden;
margin: 0;
}
.copyright-line {
display: grid;
grid-template-columns: 1fr max-content 1fr;
grid-column-gap: 24px;
padding-bottom: 15px;
text-align: center;
:before {
background-color: #e1e1e1;
content: "";
height: 2px;
margin-top: 9px;
float: right;
}
:after {
background-color: #e1e1e1;
content: "";
height: 2px;
margin-top: 9px;
float: left;
}
}
padding: ${isMobileOnly ? "48px 0 0" : "80px 147px 0"};
`;
const Style = styled.div`
margin-top: 8px;
text-align: center;
`;
const VersionStyle = styled.div`
padding: 8px 0px 20px 0px;
`;
const Body = ({ t, personal, version }) => {
const Body = ({ t, personal, versionAppServer }) => {
useEffect(() => {
setDocumentTitle(t("Common:About"));
}, [t]);
const gitHub = "GitHub";
const license = "AGPL-3.0";
const link = "www.onlyoffice.com";
const phone = "+371 660-16425";
const supportLink = "support@onlyoffice.com";
const address =
"20A-12 Ernesta Birznieka-Upisha street, Riga, Latvia, EU, LV-1050";
const licenseContent = (
<Text as="div" className="text_style" fontSize="12px">
<Trans t={t} i18nKey="LicensedUnder" ns="About">
"This software is licensed under:"
<Link
href="https://www.gnu.org/licenses/gpl-3.0.html"
isHovered={true}
fontSize="12px"
target="_blank"
>
{{ license }}
</Link>
</Trans>
</Text>
);
return (
<BodyStyle>
<div className="avatar">
{personal ? (
<ReactSVG src="images/logo_personal_about.svg" />
) : (
<img
className="logo-img"
src="images/dark_general.png"
width="320"
height="181"
alt="Logo"
/>
)}
</div>
<VersionStyle>
<Text className="text_style" fontSize="14px" color="#A3A9AE">
{`${t("Common:Version")}: ${version}`}
</Text>
</VersionStyle>
<Text className="copyright-line" fontSize="14px">
{t("AboutCompanyLicensor")}
<Text fontSize="32px" fontWeight="600">
{t("AboutHeader")}
</Text>
<Text as="div" className="text_style" fontSize="16px" isBold={true}>
<Trans t={t} i18nKey="AllRightsReservedCustomMode" ns="About">
Ascensio System SIA
<p className="hidden-text">All rights reserved.</p>
</Trans>
</Text>
<Style>
<Text className="text_style" fontSize="12px">
<Text
className="text_style"
fontSize="12px"
as="span"
color="#A3A9AE"
>
{t("AboutCompanyAddressTitle")}:{" "}
</Text>
{address}
</Text>
<Text fontSize="12px" className="text_style" as="span" color="#A3A9AE">
{t("AboutCompanyEmailTitle")}:{" "}
<Link href="mailto:support@onlyoffice.com" fontSize="12px">
{supportLink}
</Link>
</Text>
<div className="text-about">
<Text className="text_style" fontSize="12px">
<Text
fontSize="12px"
className="text_style"
as="span"
color="#A3A9AE"
>
{t("AboutCompanyTelTitle")}:{" "}
</Text>
{phone}
</Text>
</div>
<Link href="http://www.onlyoffice.com" fontSize="12px" target="_blank">
{link}
</Link>
<div className="text-license">
<div className="text-row">{licenseContent}</div>
<Text className="text_style" fontSize="12px">
{t("SourceCode")}:{" "}
<Link
href="https://github.com/ONLYOFFICE/AppServer"
isHovered={true}
fontSize="12px"
target="_blank"
>
{gitHub}
</Link>
</Text>
</div>
</Style>
<AboutContent personal={personal} versionAppServer={versionAppServer} />
</BodyStyle>
);
};
const BodyWrapper = inject(({ auth }) => ({
personal: auth.settingsStore,
version: auth.settingsStore.version,
versionAppServer: auth.settingsStore.version,
}))(withTranslation(["About", "Common"])(withLoader(observer(Body))));
const About = (props) => {