Merge branch 'feature/files' into feature/media-viewer
51
common/ASC.Api.Core/Core/Update.cs
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
*
|
||||
* (c) Copyright Ascensio System Limited 2010-2020
|
||||
*
|
||||
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
|
||||
* In accordance with Section 7(a) of the GNU GPL 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 more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
|
||||
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
|
||||
*
|
||||
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
|
||||
* relevant author attributions when distributing the software. If the display of the logo in its graphic
|
||||
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
|
||||
* in every copy of the program you distribute.
|
||||
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
namespace ASC.Api.Utils
|
||||
{
|
||||
public static class Update
|
||||
{
|
||||
public static T IfNotEquals<T>(T current, T @new)
|
||||
{
|
||||
if (!Equals(current, @new))
|
||||
{
|
||||
return @new;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
public static T IfNotEmptyAndNotEquals<T>(T current, T @new)
|
||||
{
|
||||
if (Equals(@new, default(T))) return current;
|
||||
|
||||
if (!Equals(current, @new))
|
||||
{
|
||||
return @new;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
}
|
@ -38,6 +38,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="JWT" Version="6.1.4" />
|
||||
<PackageReference Include="log4net" Version="2.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
|
||||
|
@ -27,11 +27,12 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using ASC.Data.Storage;
|
||||
|
||||
public static class StreamExtension
|
||||
{
|
||||
private const int BufferSize = 2048; //NOTE: set to 2048 to fit in minimum tcp window
|
||||
public const int BufferSize = 2048; //NOTE: set to 2048 to fit in minimum tcp window
|
||||
|
||||
public static Stream GetBuffered(this Stream srcStream)
|
||||
{
|
||||
|
104
common/ASC.Common/Utils/JsonWebToken.cs
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
*
|
||||
* (c) Copyright Ascensio System Limited 2010-2018
|
||||
*
|
||||
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
|
||||
* In accordance with Section 7(a) of the GNU GPL 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 more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
|
||||
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
|
||||
*
|
||||
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
|
||||
* relevant author attributions when distributing the software. If the display of the logo in its graphic
|
||||
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
|
||||
* in every copy of the program you distribute.
|
||||
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
using System;
|
||||
|
||||
using JWT;
|
||||
using JWT.Algorithms;
|
||||
using JWT.Serializers;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
using Formatting = Newtonsoft.Json.Formatting;
|
||||
|
||||
namespace ASC.Web.Core.Files
|
||||
{
|
||||
public class JsonWebToken
|
||||
{
|
||||
public static string Encode(object payload, string key)
|
||||
{
|
||||
var (serializer, algorithm, urlEncoder) = GetSettings();
|
||||
var encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
|
||||
|
||||
return encoder.Encode(payload, key);
|
||||
}
|
||||
|
||||
public static string Decode(string token, string key, bool verify = true, bool baseSerializer = false)
|
||||
{
|
||||
var (serializer, algorithm, urlEncoder) = GetSettings(baseSerializer);
|
||||
|
||||
var provider = new UtcDateTimeProvider();
|
||||
IJwtValidator validator = new JwtValidator(serializer, provider);
|
||||
|
||||
var decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
|
||||
|
||||
return decoder.Decode(token, key, verify);
|
||||
}
|
||||
|
||||
private static (IJsonSerializer, IJwtAlgorithm, IBase64UrlEncoder) GetSettings(bool baseSerializer = false)
|
||||
{
|
||||
return (baseSerializer ? (IJsonSerializer)new JsonNetSerializer() : new JwtSerializer(), new HMACSHA256Algorithm(), new JwtBase64UrlEncoder());
|
||||
}
|
||||
}
|
||||
|
||||
public class JwtSerializer : IJsonSerializer
|
||||
{
|
||||
private class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
|
||||
{
|
||||
protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
|
||||
{
|
||||
var contract = base.CreateDictionaryContract(objectType);
|
||||
|
||||
contract.DictionaryKeyResolver = propertyName => propertyName;
|
||||
|
||||
return contract;
|
||||
}
|
||||
}
|
||||
|
||||
public string Serialize(object obj)
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCaseExceptDictionaryKeysResolver(),
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
};
|
||||
|
||||
return JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
|
||||
}
|
||||
|
||||
public T Deserialize<T>(string json)
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCaseExceptDictionaryKeysResolver(),
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
};
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(json, settings);
|
||||
}
|
||||
}
|
||||
}
|
@ -370,19 +370,13 @@ namespace ASC.Core.Common.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
public class ConsumerFactory
|
||||
public class ConsumerFactory : IDisposable
|
||||
{
|
||||
public ILifetimeScope Builder { get; set; }
|
||||
|
||||
public ConsumerFactory(IContainer builder)
|
||||
{
|
||||
Builder = builder;
|
||||
}
|
||||
|
||||
|
||||
public ConsumerFactory(ILifetimeScope builder)
|
||||
{
|
||||
Builder = builder;
|
||||
Builder = builder.BeginLifetimeScope();
|
||||
}
|
||||
|
||||
public Consumer GetByKey(string key)
|
||||
@ -419,6 +413,11 @@ namespace ASC.Core.Common.Configuration
|
||||
{
|
||||
return Builder.Resolve<IEnumerable<T>>();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Builder.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConsumerFactoryExtension
|
||||
|
@ -19,7 +19,6 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="JWT" Version="1.3.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -72,7 +72,7 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
{
|
||||
public static DIHelper AddBoxLoginProviderService(this DIHelper services)
|
||||
{
|
||||
services.TryAddScoped<BoxLoginProvider>();
|
||||
//services.TryAddScoped<BoxLoginProvider>();
|
||||
return services
|
||||
.AddConsumerFactoryService()
|
||||
.AddKafkaService()
|
||||
|
@ -132,7 +132,7 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
{
|
||||
public static DIHelper AddDocuSignLoginProviderService(this DIHelper services)
|
||||
{
|
||||
services.TryAddScoped<DocuSignLoginProvider>();
|
||||
//services.TryAddScoped<DocuSignLoginProvider>();
|
||||
return services
|
||||
.AddConsumerFactoryService()
|
||||
.AddKafkaService()
|
||||
|
@ -185,7 +185,7 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
{
|
||||
public static DIHelper AddGoogleLoginProviderService(this DIHelper services)
|
||||
{
|
||||
services.TryAddScoped<GoogleLoginProvider>();
|
||||
//services.TryAddScoped<GoogleLoginProvider>();
|
||||
return services
|
||||
.AddConsumerFactoryService()
|
||||
.AddKafkaService()
|
||||
|
@ -40,8 +40,7 @@ using ASC.Core.Common.Configuration;
|
||||
using ASC.FederatedLogin.Helpers;
|
||||
using ASC.FederatedLogin.Profile;
|
||||
using ASC.Security.Cryptography;
|
||||
|
||||
using JWT;
|
||||
using ASC.Web.Core.Files;
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
@ -212,7 +211,7 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
|
||||
public override LoginProfile GetLoginProfile(string accessToken)
|
||||
{
|
||||
var tokenPayloadString = JsonWebToken.Decode(accessToken, string.Empty, false);
|
||||
var tokenPayloadString = JsonWebToken.Decode(accessToken, string.Empty, false, true);
|
||||
var tokenPayload = JObject.Parse(tokenPayloadString);
|
||||
if (tokenPayload == null)
|
||||
{
|
||||
|
@ -75,7 +75,7 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
{
|
||||
public static DIHelper AddOneDriveLoginProviderService(this DIHelper services)
|
||||
{
|
||||
services.TryAddScoped<OneDriveLoginProvider>();
|
||||
//services.TryAddScoped<OneDriveLoginProvider>();
|
||||
return services
|
||||
.AddConsumerFactoryService()
|
||||
.AddKafkaService()
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
using System;
|
||||
|
||||
using ASC.Core;
|
||||
using ASC.Core.Common.EF;
|
||||
using ASC.Core.Common.EF.Context;
|
||||
|
||||
@ -37,10 +38,10 @@ namespace ASC.VoipService.Dao
|
||||
|
||||
protected VoipDbContext VoipDbContext { get; set; }
|
||||
|
||||
protected AbstractDao(DbContextManager<VoipDbContext> dbOptions, int tenantID)
|
||||
protected AbstractDao(DbContextManager<VoipDbContext> dbOptions, TenantManager tenantManager)
|
||||
{
|
||||
VoipDbContext = dbOptions.Get(dbid);
|
||||
TenantID = tenantID;
|
||||
TenantID = tenantManager.GetCurrentTenant().TenantId;
|
||||
}
|
||||
|
||||
protected int TenantID
|
||||
|
@ -29,6 +29,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
using ASC.Common;
|
||||
using ASC.Common.Caching;
|
||||
using ASC.Core;
|
||||
using ASC.Core.Common;
|
||||
@ -65,7 +66,7 @@ namespace ASC.VoipService.Dao
|
||||
public VoipDaoCache VoipDaoCache { get; }
|
||||
|
||||
public CachedVoipDao(
|
||||
int tenantID,
|
||||
TenantManager tenantManager,
|
||||
DbContextManager<VoipDbContext> dbOptions,
|
||||
AuthContext authContext,
|
||||
TenantUtil tenantUtil,
|
||||
@ -73,7 +74,7 @@ namespace ASC.VoipService.Dao
|
||||
BaseCommonLinkUtility baseCommonLinkUtility,
|
||||
ConsumerFactory consumerFactory,
|
||||
VoipDaoCache voipDaoCache)
|
||||
: base(tenantID, dbOptions, authContext, tenantUtil, securityContext, baseCommonLinkUtility, consumerFactory)
|
||||
: base(tenantManager, dbOptions, authContext, tenantUtil, securityContext, baseCommonLinkUtility, consumerFactory)
|
||||
{
|
||||
cache = voipDaoCache.Cache;
|
||||
VoipDaoCache = voipDaoCache;
|
||||
@ -109,4 +110,21 @@ namespace ASC.VoipService.Dao
|
||||
return "voip" + tenant.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static class VoipDaoExtention
|
||||
{
|
||||
public static DIHelper AddVoipDaoService(this DIHelper services)
|
||||
{
|
||||
services.TryAddScoped<VoipDao, CachedVoipDao>();
|
||||
services.TryAddSingleton<VoipDaoCache>();
|
||||
|
||||
return services
|
||||
.AddDbContextManagerService<VoipDbContext>()
|
||||
.AddAuthContextService()
|
||||
.AddTenantUtilService()
|
||||
.AddSecurityContextService()
|
||||
.AddBaseCommonLinkUtilityService()
|
||||
.AddConsumerFactoryService();
|
||||
}
|
||||
}
|
||||
}
|
@ -42,14 +42,14 @@ namespace ASC.VoipService.Dao
|
||||
public class VoipDao : AbstractDao
|
||||
{
|
||||
public VoipDao(
|
||||
int tenantID,
|
||||
TenantManager tenantManager,
|
||||
DbContextManager<VoipDbContext> dbOptions,
|
||||
AuthContext authContext,
|
||||
TenantUtil tenantUtil,
|
||||
SecurityContext securityContext,
|
||||
BaseCommonLinkUtility baseCommonLinkUtility,
|
||||
ConsumerFactory consumerFactory)
|
||||
: base(dbOptions, tenantID)
|
||||
: base(dbOptions, tenantManager)
|
||||
{
|
||||
AuthContext = authContext;
|
||||
TenantUtil = tenantUtil;
|
||||
|
@ -43,6 +43,45 @@
|
||||
"sign": ""
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"thirdparty": {
|
||||
"enable" : ["box", "dropboxv2", "docusign", "google", "onedrive", "sharepoint", "nextcloud", "owncloud", "webdav", "kdrive", "yandex"]
|
||||
},
|
||||
"docservice": {
|
||||
"coauthor-docs" : [ ".pptx", ".ppsx", ".xlsx", ".csv", ".docx", ".txt" ],
|
||||
"commented-docs" : [ ".docx", ".xlsx", ".pptx" ],
|
||||
"convert-docs" : [ ".pptm",".ppt",".ppsm",".pps",".potx",".potm",".pot",".odp",".fodp",".otp",".xlsm",".xls",".xltx",".xltm",".xlt",".ods",".fods",".ots",".docm",".doc",".dotx",".dotm",".dot",".odt",".fodt",".ott",".rtf" ],
|
||||
"edited-docs" : [ ".pptx",".pptm",".ppt",".ppsx",".ppsm",".pps",".potx",".potm",".pot",".odp",".fodp",".otp",".xlsx",".xlsm",".xls",".xltx",".xltm",".xlt",".ods",".fods",".ots",".csv",".docx",".docm",".doc",".dotx",".dotm",".dot",".odt",".fodt",".ott",".txt",".rtf",".mht",".html",".htm" ],
|
||||
"encrypted-docs" : [ ".docx",".xlsx",".pptx" ],
|
||||
"formfilling-docs" : [ ".docx" ],
|
||||
"reviewed-docs" : [ ".docx" ],
|
||||
"viewed-docs" : [ ".pptx",".pptm",".ppt",".ppsx",".ppsm",".pps",".potx",".potm",".pot",".odp",".fodp",".otp",".gslides",".xlsx",".xlsm",".xls",".xltx",".xltm",".xlt",".ods",".fods",".ots",".gsheet",".csv",".docx",".docm",".doc",".dotx",".dotm",".dot",".odt",".fodt",".ott",".gdoc",".txt",".rtf",".mht",".html",".htm",".epub",".pdf",".djvu",".xps" ],
|
||||
"secret" :
|
||||
{
|
||||
"value": "",
|
||||
"header": "",
|
||||
},
|
||||
"url":
|
||||
{
|
||||
"public": "http://192.168.3.142/",
|
||||
"internal": "",
|
||||
"portal": ""
|
||||
}
|
||||
},
|
||||
"ffmpeg" :
|
||||
{
|
||||
"value": "",
|
||||
"exts": [ "avi", "mpeg", "mpg", "wmv" ]
|
||||
},
|
||||
"uploader":
|
||||
{
|
||||
"chunk-size": 10485760,
|
||||
"url": "products/files/"
|
||||
},
|
||||
"viewed-images": [".bmp",".gif",".jpeg",".jpg",".png",".ico",".tif",".tiff",".webp"],
|
||||
"viewed-media": [".aac",".flac",".m4a",".mp3",".oga",".ogg",".wav",".f4v",".m4v",".mov",".mp4",".ogv",".webm"],
|
||||
"index": [".pptx",".pptm",".ppt",".ppsx",".ppsm",".pps",".potx",".potm",".pot",".odp",".fodp",".otp",".gslides",".xlsx",".xlsm",".xls",".xltx",".xltm",".xlt",".ods",".fods",".ots",".gsheet",".csv",".docx",".docm",".doc",".dotx",".dotm",".dot",".odt",".fodt",".ott",".gdoc",".txt",".rtf",".mht",".html",".htm",".epub",".pdf",".djvu",".xps"],
|
||||
},
|
||||
"web": {
|
||||
"api": "api/2.0",
|
||||
"alias": {
|
||||
|
@ -23,5 +23,18 @@
|
||||
"notify": {
|
||||
"postman": "services"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"docservice": {
|
||||
"secret" : {
|
||||
"value": "SQyTqextlJFq",
|
||||
"header": "AuthorizationJwt"
|
||||
},
|
||||
"url": {
|
||||
"public" : "https://dotnet.onlyoffice.com:8093",
|
||||
"internal" : "",
|
||||
"portal" : "",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -141,6 +141,11 @@ server {
|
||||
root $public_root;
|
||||
try_files /$basename /index.html =404;
|
||||
}
|
||||
|
||||
location ~* (/httphandlers/filehandler.ashx|ChunkedUploader.ashx) {
|
||||
proxy_pass http://localhost:5007;
|
||||
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 5.6 KiB |
BIN
products/ASC.Files/Client/public/images/empty_screen_forme.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
@ -0,0 +1,3 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#83888d" d="M10 10v-1.5c0.459-0.185 0.815-0.541 0.996-0.988l0.004-0.012v-3c0-1-1-2.5-2-2.5h-2c-1 0-2 1.5-2 2.5v3c0.185 0.459 0.541 0.815 0.988 0.996l0.012 0.004v1.5s-4 2-4 3v1h12v-1c0-1-4-3-4-3z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 321 B |
BIN
products/ASC.Files/Client/public/images/empty_screen_trash.png
Normal file
After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 478 B After Width: | Height: | Size: 478 B |
Before Width: | Height: | Size: 532 B After Width: | Height: | Size: 532 B |
Before Width: | Height: | Size: 547 B After Width: | Height: | Size: 547 B |
Before Width: | Height: | Size: 686 B After Width: | Height: | Size: 686 B |
@ -19,6 +19,7 @@
|
||||
<link rel="apple-touch-icon" href="icon.png">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i' rel='stylesheet' type='text/css'></link>
|
||||
<script type="text/javascript" src="https://dotnet.onlyoffice.com/ds-vpath/web-apps/apps/api/documents/api.js?ver=10.0.1.875"></script>
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
|
@ -3,6 +3,7 @@ import { connect } from "react-redux";
|
||||
import { Router, Switch, Redirect } from "react-router-dom";
|
||||
import { Loader } from "asc-web-components";
|
||||
import Home from "./components/pages/Home";
|
||||
import DocEditor from "./components/pages/DocEditor";
|
||||
import { history, PrivateRoute, PublicRoute, Login, Error404, StudioLayout, Offline } from "asc-web-common";
|
||||
|
||||
const App = ({ settings }) => {
|
||||
@ -17,6 +18,7 @@ const App = ({ settings }) => {
|
||||
<Switch>
|
||||
<Redirect exact from="/" to={`${homepage}`} />
|
||||
<PrivateRoute exact path={[homepage, `${homepage}/filter`]} component={Home} />
|
||||
<PrivateRoute exact path={`${homepage}/doceditor`} component={DocEditor} />
|
||||
<PublicRoute exact path={["/login","/login/error=:error", "/login/confirmed-email=:confirmedEmail"]} component={Login} />
|
||||
<PrivateRoute component={Error404} />
|
||||
</Switch>
|
||||
|
@ -58,10 +58,11 @@ class TreeFolders extends React.Component {
|
||||
|
||||
onSelect = data => {
|
||||
if (this.props.selectedKeys[0] !== data[0]) {
|
||||
this.props.onLoading(true);
|
||||
const newFilter = this.props.filter.clone();
|
||||
fetchFiles(data[0], newFilter, store.dispatch).catch(err =>
|
||||
toastr.error("Something went wrong", err)
|
||||
);
|
||||
).finally(() => this.props.onLoading(false));
|
||||
}
|
||||
|
||||
//this.props.selectFolder(data && data.length === 1 && data[0] !== "root" ? data[0] : null);
|
||||
@ -171,7 +172,7 @@ class TreeFolders extends React.Component {
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { expandedKeys, data } = this.props;
|
||||
const { expandedKeys, data, onLoading } = this.props;
|
||||
if (this.state.expandedKeys.length !== expandedKeys.length) {
|
||||
this.setState({ expandedKeys });
|
||||
}
|
||||
|
@ -2,37 +2,231 @@ import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router';
|
||||
import { MainButton, DropDownItem, toastr, utils } from "asc-web-components";
|
||||
import { withTranslation, I18nextProvider } from "react-i18next";
|
||||
import {
|
||||
MainButton,
|
||||
DropDownItem,
|
||||
toastr
|
||||
} from "asc-web-components";
|
||||
import { withTranslation, I18nextProvider } from 'react-i18next';
|
||||
import { setAction } from '../../../store/files/actions';
|
||||
import { isCanCreate } from '../../../store/files/selectors';
|
||||
import i18n from '../i18n';
|
||||
import { utils, constants } from 'asc-web-common';
|
||||
setAction,
|
||||
fetchFiles,
|
||||
setTreeFolders,
|
||||
} from "../../../store/files/actions";
|
||||
import { isCanCreate, loopTreeFolders } from "../../../store/files/selectors";
|
||||
import store from "../../../store/store";
|
||||
import i18n from "../i18n";
|
||||
import { utils as commonUtils, constants, api } from "asc-web-common";
|
||||
|
||||
const { changeLanguage } = utils;
|
||||
const { changeLanguage } = commonUtils;
|
||||
const { FileAction } = constants;
|
||||
|
||||
class PureArticleMainButtonContent extends React.Component {
|
||||
state = {
|
||||
files: [],
|
||||
uploadedFiles: 0,
|
||||
totalSize: 0,
|
||||
percent: 0
|
||||
};
|
||||
|
||||
onCreate = (format) => {
|
||||
this.props.setAction(
|
||||
{
|
||||
this.props.setAction({
|
||||
type: FileAction.Create,
|
||||
extension: format,
|
||||
id: -1
|
||||
id: -1,
|
||||
});
|
||||
};
|
||||
|
||||
onUploadFileClick = () => this.inputFilesElement.click();
|
||||
|
||||
onUploadFolderClick = () => this.inputFolderElement.click();
|
||||
|
||||
updateFiles = () => {
|
||||
const { onLoading, filter, currentFolderId, treeFolders, setTreeFolders } = this.props;
|
||||
|
||||
onLoading(true);
|
||||
const newFilter = filter.clone();
|
||||
fetchFiles(currentFolderId, newFilter, store.dispatch, treeFolders)
|
||||
.then((data) => {
|
||||
const path = data.selectedFolder.pathParts;
|
||||
const newTreeFolders = treeFolders;
|
||||
const folders = data.selectedFolder.folders;
|
||||
const foldersCount = data.selectedFolder.foldersCount;
|
||||
loopTreeFolders(path, newTreeFolders, folders, foldersCount);
|
||||
setTreeFolders(newTreeFolders);
|
||||
})
|
||||
.catch((err) => toastr.error(err))
|
||||
.finally(() => {
|
||||
onLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
sendChunk = (files, location, requestsDataArray, isLatestFile, indexOfFile) => {
|
||||
const sendRequestFunc = (index) => {
|
||||
let newState = {};
|
||||
api.files
|
||||
.uploadFile(location, requestsDataArray[index])
|
||||
.then((res) => {
|
||||
let newPercent = this.state.percent;
|
||||
const percent = (newPercent +=
|
||||
(files[indexOfFile].size / this.state.totalSize) * 100);
|
||||
if (res.data.data && res.data.data.uploaded) {
|
||||
files[indexOfFile].uploaded = true;
|
||||
newState = { files, percent };
|
||||
}
|
||||
if (index + 1 !== requestsDataArray.length) {
|
||||
sendRequestFunc(index + 1);
|
||||
} else if (isLatestFile) {
|
||||
this.updateFiles();
|
||||
newState = Object.assign({}, newState, {
|
||||
uploadedFiles: this.state.uploadedFiles + 1,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
newState = Object.assign({}, newState, {
|
||||
uploadedFiles: this.state.uploadedFiles + 1,
|
||||
});
|
||||
this.startSessionFunc(indexOfFile + 1);
|
||||
}
|
||||
})
|
||||
.catch((err) => toastr.error(err))
|
||||
.finally(() => {
|
||||
if (
|
||||
newState.hasOwnProperty("files") ||
|
||||
newState.hasOwnProperty("percent") ||
|
||||
newState.hasOwnProperty("uploadedFiles")
|
||||
) {
|
||||
let progressVisible = true;
|
||||
let uploadedFiles = newState.uploadedFiles;
|
||||
let percent = newState.percent;
|
||||
if (newState.uploadedFiles === files.length) {
|
||||
percent = 100;
|
||||
newState.percent = 0;
|
||||
newState.uploadedFiles = 0;
|
||||
progressVisible = false;
|
||||
}
|
||||
this.setState(newState, () => {
|
||||
this.props.setProgressValue(percent);
|
||||
this.props.setProgressLabel(
|
||||
this.props.t("UploadingLabel", {
|
||||
file: uploadedFiles,
|
||||
totalFiles: files.length,
|
||||
})
|
||||
);
|
||||
if (!progressVisible) {
|
||||
this.props.setProgressVisible(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
sendRequestFunc(0);
|
||||
};
|
||||
|
||||
startSessionFunc = indexOfFile => {
|
||||
const { files } = this.state;
|
||||
const { currentFolderId } = this.props;
|
||||
const file = files[indexOfFile];
|
||||
const isLatestFile = indexOfFile === files.length - 1;
|
||||
|
||||
const fileName = file.name;
|
||||
const fileSize = file.size;
|
||||
const relativePath = file.webkitRelativePath
|
||||
? file.webkitRelativePath.slice(0, -file.name.length)
|
||||
: "";
|
||||
|
||||
let location;
|
||||
const requestsDataArray = [];
|
||||
const chunkSize = 1024 * 1023; //~0.999mb
|
||||
const chunks = Math.ceil(file.size / chunkSize, chunkSize);
|
||||
let chunk = 0;
|
||||
|
||||
api.files
|
||||
.startUploadSession(currentFolderId, fileName, fileSize, relativePath)
|
||||
.then((res) => {
|
||||
location = res.data.location;
|
||||
while (chunk < chunks) {
|
||||
const offset = chunk * chunkSize;
|
||||
//console.log("current chunk..", chunk);
|
||||
//console.log("file blob from offset...", offset);
|
||||
//console.log(file.slice(offset, offset + chunkSize));
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file.slice(offset, offset + chunkSize));
|
||||
requestsDataArray.push(formData);
|
||||
chunk++;
|
||||
}
|
||||
})
|
||||
.then(() =>
|
||||
this.sendChunk(
|
||||
files,
|
||||
location,
|
||||
requestsDataArray,
|
||||
isLatestFile,
|
||||
indexOfFile
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
onFileChange = (e) => {
|
||||
const { t, setProgressVisible, setProgressLabel } = this.props;
|
||||
const files = e.target.files;
|
||||
//console.log("files", files);
|
||||
const newFiles = [];
|
||||
if(files) {
|
||||
let total = 0;
|
||||
for (let item of files) {
|
||||
if (item.size !== 0) {
|
||||
newFiles.push(item);
|
||||
total += item.size;
|
||||
} else {
|
||||
toastr.error(t("ErrorUploadMessage"));
|
||||
}
|
||||
}
|
||||
|
||||
if (newFiles.length > 0) {
|
||||
this.setState({ files: newFiles, totalSize: total }, () => {
|
||||
setProgressVisible(true);
|
||||
setProgressLabel(
|
||||
this.props.t("UploadingLabel", {
|
||||
file: 0,
|
||||
totalFiles: newFiles.length,
|
||||
})
|
||||
);
|
||||
this.startSessionFunc(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onInputClick = e => {
|
||||
e.target.value = null;
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return nextProps.isCanCreate !== this.props.isCanCreate;
|
||||
const { files, uploadedFiles, totalSize, percent } = this.state;
|
||||
if (nextProps.isCanCreate !== this.props.isCanCreate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!utils.array.isArrayEqual(nextState.files, files)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextState.uploadedFiles !== uploadedFiles) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextState.totalSize !== totalSize) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nextState.percent !== percent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log("Files ArticleMainButtonContent render");
|
||||
//console.log("Files ArticleMainButtonContent render");
|
||||
const { t, isCanCreate } = this.props;
|
||||
|
||||
return (
|
||||
@ -64,14 +258,39 @@ class PureArticleMainButtonContent extends React.Component {
|
||||
<DropDownItem isSeparator />
|
||||
<DropDownItem
|
||||
icon="ActionsUploadIcon"
|
||||
label={t('Upload')}
|
||||
onClick={() => toastr.info("Upload click")}
|
||||
disabled
|
||||
label={t("UploadFiles")}
|
||||
onClick={this.onUploadFileClick}
|
||||
/>
|
||||
<DropDownItem
|
||||
icon="ActionsUploadIcon"
|
||||
label={t("UploadFolder")}
|
||||
onClick={this.onUploadFolderClick}
|
||||
/>
|
||||
<input
|
||||
id="customFile"
|
||||
className="custom-file-input"
|
||||
multiple
|
||||
type="file"
|
||||
onChange={this.onFileChange}
|
||||
onClick={this.onInputClick}
|
||||
ref={(input) => (this.inputFilesElement = input)}
|
||||
style={{ display: "none" }}
|
||||
/>
|
||||
<input
|
||||
id="customFile"
|
||||
className="custom-file-input"
|
||||
webkitdirectory=""
|
||||
mozdirectory=""
|
||||
type="file"
|
||||
onChange={this.onFileChange}
|
||||
onClick={this.onInputClick}
|
||||
ref={(input) => (this.inputFolderElement = input)}
|
||||
style={{ display: "none" }}
|
||||
/>
|
||||
</MainButton>
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const ArticleMainButtonContentContainer = withTranslation()(PureArticleMainButtonContent);
|
||||
|
||||
@ -86,11 +305,18 @@ ArticleMainButtonContent.propTypes = {
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { selectedFolder, filter, treeFolders } = state.files;
|
||||
const { settings, user } = state.auth;
|
||||
|
||||
return {
|
||||
settings: state.auth.settings,
|
||||
isCanCreate: isCanCreate(state.files.selectedFolder, state.auth.user)
|
||||
}
|
||||
}
|
||||
settings,
|
||||
isCanCreate: isCanCreate(selectedFolder, user),
|
||||
currentFolderId: selectedFolder.id,
|
||||
filter,
|
||||
treeFolders,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, { setAction })(withRouter(ArticleMainButtonContent));
|
||||
export default connect(mapStateToProps, { setAction, setTreeFolders })(
|
||||
withRouter(ArticleMainButtonContent)
|
||||
);
|
||||
|
@ -4,5 +4,8 @@
|
||||
"NewSpreadSheet": "New Spreadsheet",
|
||||
"NewPresentation": "New Presentation",
|
||||
"NewFolder": "New Folder",
|
||||
"Upload": "Upload"
|
||||
"UploadFiles": "Upload files",
|
||||
"UploadFolder": "Upload folder",
|
||||
"ErrorUploadMessage": "You cannot upload a folder or an empty file",
|
||||
"UploadingLabel": "Uploading files: {{file}} of {{totalFiles}}"
|
||||
}
|
@ -4,5 +4,8 @@
|
||||
"NewSpreadsheet": "Новая таблица",
|
||||
"NewPresentation": "Новая презентация",
|
||||
"NewFolder": "Новая папка",
|
||||
"Upload": "Загрузить"
|
||||
"UploadFiles": "Загрузить файлы",
|
||||
"UploadFolder": "Загрузить папку",
|
||||
"ErrorUploadMessage": "Нельзя загрузить папку или пустой файл",
|
||||
"UploadingLabel": "Загружено файлов: {{file}} из {{totalFiles}}"
|
||||
}
|
@ -71,9 +71,9 @@ class DeleteDialogComponent extends React.Component {
|
||||
let i = 0;
|
||||
while (selection.length !== i) {
|
||||
if (selection[i].fileExst && selection[i].checked) {
|
||||
fileIds.push(selection[i].id.toString());
|
||||
fileIds.push(selection[i].id);
|
||||
} else if (selection[i].checked) {
|
||||
folderIds.push(selection[i].id.toString());
|
||||
folderIds.push(selection[i].id);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
@ -4,12 +4,14 @@ import { toastr, ModalDialog, Button, Text } from "asc-web-components";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import i18n from "./i18n";
|
||||
import { api, utils } from "asc-web-common";
|
||||
import { fetchFiles } from "../../../store/files/actions";
|
||||
import store from "../../../store/store";
|
||||
|
||||
const { files } = api;
|
||||
const { changeLanguage } = utils;
|
||||
|
||||
const EmptyTrashDialogComponent = props => {
|
||||
const { onClose, visible, t } = props;
|
||||
const { onClose, visible, t, filter, currentFolderId } = props;
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
changeLanguage(i18n);
|
||||
@ -21,6 +23,7 @@ const EmptyTrashDialogComponent = props => {
|
||||
files
|
||||
.emptyTrash()
|
||||
.then(res => {
|
||||
fetchFiles(currentFolderId, filter, store.dispatch)
|
||||
toastr.success(successMessage);
|
||||
}) //toastr.success("It was successfully deleted 24 from 24"); + progressBar
|
||||
.catch(err => toastr.error(err))
|
||||
@ -28,7 +31,7 @@ const EmptyTrashDialogComponent = props => {
|
||||
setIsLoading(false);
|
||||
onClose();
|
||||
});
|
||||
}, [onClose]);
|
||||
}, [onClose, filter, currentFolderId]);
|
||||
|
||||
return (
|
||||
<ModalDialogContainer>
|
||||
|
@ -0,0 +1,54 @@
|
||||
import i18n from "i18next";
|
||||
import Backend from "i18next-xhr-backend";
|
||||
import config from "../../../../package.json";
|
||||
import { constants } from 'asc-web-common';
|
||||
const { LANGUAGE } = constants;
|
||||
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
newInstance
|
||||
.use(Backend)
|
||||
.init({
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
},
|
||||
backend: {
|
||||
loadPath: `${config.homepage}/locales/DocEditor/{{lng}}/{{ns}}.json`
|
||||
}
|
||||
});
|
||||
} else if (process.env.NODE_ENV === "development") {
|
||||
|
||||
const resources = {
|
||||
en: {
|
||||
translation: require("./locales/en/translation.json")
|
||||
},
|
||||
ru: {
|
||||
translation: require("./locales/ru/translation.json")
|
||||
}
|
||||
};
|
||||
|
||||
newInstance.init({
|
||||
resources: resources,
|
||||
lng: localStorage.getItem(LANGUAGE) || 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default newInstance;
|
@ -0,0 +1,74 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
import { withRouter } from "react-router";
|
||||
import { RequestLoader } from "asc-web-components";
|
||||
import { utils, api } from "asc-web-common";
|
||||
import { withTranslation, I18nextProvider } from 'react-i18next';
|
||||
import i18n from "./i18n";
|
||||
|
||||
const { changeLanguage, getObjectByLocation } = utils;
|
||||
const { files } = api;
|
||||
|
||||
class PureEditor extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isLoading: false
|
||||
};
|
||||
}
|
||||
|
||||
onLoading = status => {
|
||||
this.setState({ isLoading: status });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isLoading } = this.state;
|
||||
const { t } = this.props;
|
||||
|
||||
const urlParams = getObjectByLocation(window.location);
|
||||
const fileId = urlParams.fileId || null;
|
||||
|
||||
files.openEdit(fileId)
|
||||
.then(config => window.DocsAPI.DocEditor("editor", config));
|
||||
|
||||
return (
|
||||
<>
|
||||
<RequestLoader
|
||||
visible={isLoading}
|
||||
zIndex={256}
|
||||
loaderSize='16px'
|
||||
loaderColor={"#999"}
|
||||
label={`${t('LoadingProcessing')} ${t('LoadingDescription')}`}
|
||||
fontSize='12px'
|
||||
fontColor={"#999"}
|
||||
/>
|
||||
<div id="editor"></div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const EditorContainer = withTranslation()(PureEditor);
|
||||
|
||||
const DocEditor = (props) => {
|
||||
changeLanguage(i18n);
|
||||
return (<I18nextProvider i18n={i18n}><EditorContainer {...props} /></I18nextProvider>);
|
||||
}
|
||||
|
||||
DocEditor.propTypes = {
|
||||
files: PropTypes.array,
|
||||
history: PropTypes.object,
|
||||
isLoaded: PropTypes.bool
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
files: state.files.files,
|
||||
folders: state.files.folders,
|
||||
isLoaded: state.auth.isLoaded
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(withRouter(DocEditor));
|
@ -0,0 +1,72 @@
|
||||
{
|
||||
"NewDocument": "New document",
|
||||
"NewSpreadsheet": "New spreadsheet",
|
||||
"NewPresentation": "New presentation",
|
||||
"NewFolder": "New folder",
|
||||
"UploadToFolder": "Upload to folder",
|
||||
"SharingSettings": "Sharing settings",
|
||||
"LinkForPortalUsers": "Link for portal users",
|
||||
"MoveTo": "Move to",
|
||||
"Copy": "Copy",
|
||||
"Download": "Download",
|
||||
"Rename": "Rename",
|
||||
"Delete": "Delete",
|
||||
"Type": "Type",
|
||||
"Author": "Author",
|
||||
"Search": "Search",
|
||||
"Folders": "Folders",
|
||||
"Document": "Document",
|
||||
"Documents": "Documents",
|
||||
"Spreadsheet": "Spreadsheet",
|
||||
"Presentation": "Presentation",
|
||||
"Presentations": "Presentations",
|
||||
"Spreadsheets": "Spreadsheets",
|
||||
"Images": "Images",
|
||||
"Media": "Media",
|
||||
"Archives": "Archives",
|
||||
"AllFiles": "All files",
|
||||
"NoSubfolders": "No subfolders",
|
||||
"LoadingProcessing": "Loading...",
|
||||
"LoadingDescription": "Please wait...",
|
||||
"ByLastModifiedDate": "Last modified date",
|
||||
"ByCreationDate": "Creation date",
|
||||
"ByTitle": "Title",
|
||||
"ByType": "Type",
|
||||
"BySize": "Size",
|
||||
"ByAuthor": "Author",
|
||||
"DirectionAscLabel":"A-Z",
|
||||
"DirectionDescLabel":"Z-A",
|
||||
"CountPerPage": "{{count}} per page",
|
||||
"PageOfTotalPage": "{{page}} of {{totalPage}}",
|
||||
"PreviousPage": "Previous",
|
||||
"NextPage": "Next",
|
||||
"DefaultOptionLabel": "Me",
|
||||
"LblSelect": "Select",
|
||||
"AuthorMe": "Me",
|
||||
"TitleCreated": "Created",
|
||||
"TitleUploaded": "Uploaded",
|
||||
"TitleModified": "Updated",
|
||||
"TitleRemoved": "Removed",
|
||||
"TitleSubfolders": "Flds",
|
||||
"TitleDocuments": "Dcs",
|
||||
"Share": "Share",
|
||||
"DownloadAs": "Download as",
|
||||
"More": "More",
|
||||
"CloseButton": "Close",
|
||||
"All": "All",
|
||||
"Files": "Files",
|
||||
"EmptyRecycleBin": "Empty Recycle Bin",
|
||||
"Folder": "Folder",
|
||||
"ClearButton": "Reset filter",
|
||||
"SubheadingEmptyText": "No files to be displayed in this section",
|
||||
"MyEmptyContainerDescription": "The documents and image files you create or upload to the portal are kept here in 'My Documents' section. You can open and edit them using the ONLYOFFICE™ portal editor, share them with friends or colleagues, organize into folders. Drag-and-drop the files from your computer here to upload them to your portal even more easily.",
|
||||
"SharedEmptyContainerDescription": "The 'Shared with Me' section is used to show the files which your friends or colleagues gave you access to. In case you haven't seen the latest changes in the documents they are marked 'new'. You can remove the files from the list clicking the appropriate button.",
|
||||
"CommonEmptyContainerDescription": "The 'Common Documents' section shows all the documents shared by portal administrator for common access. Only portal administrator can create folders in this section, but with the access granted the portal users can also upload their files here. Drag-and-drop the files from your computer here to upload them to your portal even more easily.",
|
||||
"TrashEmptyContainerDescription": "The 'Recycle Bin' section is where all the deleted files are moved. You can either restore them in case they are deleted by mistake or delete them permanently. Please note, that when you delete the files from the 'Recycle Bin' they cannot be restored any longer.",
|
||||
"GoToMyButton": "Go to My Documents",
|
||||
"BackToParentFolderButton" : "Back to parent folder",
|
||||
"EmptyFolderHeader": "No files in this folder",
|
||||
"EmptyFilterSubheadingText": "No files to be displayed for this filter here",
|
||||
"EmptyFilterDescriptionText": "No files or folders matching your filter can be displayed in this section. Please select other filter options or clear filter to view all the files in this section. You can also look for the file you need in other sections.",
|
||||
"Filter": "Filter"
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
{
|
||||
"NewDocument": "Новый документ",
|
||||
"NewSpreadsheet": "Новая таблица",
|
||||
"NewPresentation": "Новая презентация",
|
||||
"NewFolder": "Новая папка",
|
||||
"UploadToFolder": "Загрузить в папку",
|
||||
"SharingSettings": "Настройки доступа",
|
||||
"LinkForPortalUsers": "Ссылка для пользователей портала",
|
||||
"MoveTo": "Переместить",
|
||||
"Copy": "Копировать",
|
||||
"Download": "Скачать",
|
||||
"Rename": "Переименовать",
|
||||
"Delete": "Удалить",
|
||||
"Type": "Тип",
|
||||
"Author": "Автор",
|
||||
"Search": "Поиск",
|
||||
"Folders": "Папки",
|
||||
"Document": "Документ",
|
||||
"Documents": "Документы",
|
||||
"Spreadsheet": "Таблица",
|
||||
"Presentation": "Презентация",
|
||||
"Presentations": "Презентации",
|
||||
"Spreadsheets": "Таблицы",
|
||||
"Images": "Изображения",
|
||||
"Media": "Медиа",
|
||||
"Archives": "Архивы",
|
||||
"AllFiles": "Все файлы",
|
||||
"NoSubfolders": "Без подпапок",
|
||||
"LoadingProcessing": "Загрузка...",
|
||||
"LoadingDescription": "Пожалуйста, подождите...",
|
||||
"ByLastModifiedDate": "Дата последнего изменения",
|
||||
"ByCreationDate": "Дата создания",
|
||||
"ByTitle": "Название",
|
||||
"ByType": "Тип",
|
||||
"BySize": "Размер",
|
||||
"ByAuthor": "Автор",
|
||||
"DirectionAscLabel":"А-Я",
|
||||
"DirectionDescLabel":"Я-А",
|
||||
"CountPerPage": "{{count}} на странице",
|
||||
"PageOfTotalPage": "{{page}} из {{totalPage}}",
|
||||
"PreviousPage": "Предыдущая",
|
||||
"NextPage": "Следующая",
|
||||
"DefaultOptionLabel": "Я",
|
||||
"LblSelect": "Выберите",
|
||||
"AuthorMe": "Я",
|
||||
"TitleCreated": "Создана",
|
||||
"TitleUploaded": "Загружен",
|
||||
"TitleModified": "Обновлён",
|
||||
"TitleRemoved": "Удалён",
|
||||
"TitleSubfolders": "Flds",
|
||||
"TitleDocuments": "Dcs",
|
||||
"Share": "Общий доступ",
|
||||
"DownloadAs": "Скачать как",
|
||||
"More": "Больше",
|
||||
"CloseButton": "Закрыть",
|
||||
"All": "Все",
|
||||
"Files": "Файлы",
|
||||
"EmptyRecycleBin": "Очистить корзину",
|
||||
"Folder": "Папка",
|
||||
"ClearButton": "Сбросить фильтр",
|
||||
"SubheadingEmptyText": "Нет файлов для отображения в этом разделе",
|
||||
"MyEmptyContainerDescription": "Документы и файлы изображений, которые вы создаете или загружаете на портал, хранятся здесь, в разделе «Мои документы». Вы можете открывать и редактировать их с помощью редактора портала ONLYOFFICE ™, делиться ими с друзьями или коллегами, организовывать в папки. Перетащите файлы со своего компьютера сюда, чтобы загрузить их на свой портал еще проще.",
|
||||
"SharedEmptyContainerDescription": "Раздел «Доступно для меня» используется для отображения файлов, к которым ваши друзья или коллеги предоставили вам доступ. Если вы не видели последние изменения в документах, они помечаются как «новые». Вы можете удалить файлы из списка, нажав соответствующую кнопку.",
|
||||
"CommonEmptyContainerDescription": "В разделе «Общие документы» отображаются все документы, которыми администратор портала предоставил общий доступ. Только администраторы портала могут создавать папки в этом разделе, но с предоставленным доступом пользователи портала также могут загружать свои файлы здесь. Перетащите файлы со своего компьютера сюда, чтобы загрузить их на свой портал еще проще.",
|
||||
"TrashEmptyContainerDescription": "В разделе «Корзина» находятся все удаленные файлы. Вы можете восстановить их, если они были удалены по ошибке, или удалить их навсегда. Обратите внимание, что когда вы удаляете файлы из корзины, они больше не могут быть восстановлены.",
|
||||
"GoToMyButton": "Перейти к моим документам",
|
||||
"BackToParentFolderButton" : "Вернуться в папку на уровень выше",
|
||||
"EmptyFolderHeader": "В этой папке нет файлов",
|
||||
"EmptyFilterSubheadingText": "Здесь нет файлов, соответствующих этому фильтру",
|
||||
"EmptyFilterDescriptionText": "В этом разделе нет файлов или папок, соответствующих фильтру. Пожалуйста, выберите другие параметры или очистите фильтр, чтобы показать все файлы в этом разделе. Вы можете также поискать нужный файл в других разделах.",
|
||||
"Filter": "Фильтр"
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
import React, { memo } from "react";
|
||||
import styled from "styled-components";
|
||||
import { TextInput, Button } from "asc-web-components";
|
||||
|
||||
const EditingWrapper = styled.div`
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
height: 56px;
|
||||
}
|
||||
.edit-text {
|
||||
height: 30px;
|
||||
font-size: 15px;
|
||||
outline: 0 !important;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
font-family: 'Open Sans',sans-serif,Arial;
|
||||
text-align: left;
|
||||
color: #333333;
|
||||
}
|
||||
.edit-button {
|
||||
margin-left: 8px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.edit-ok-icon {
|
||||
margin-top: -6px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.edit-cancel-icon {
|
||||
margin-top: -6px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
const EditingWrapperComponent = props => {
|
||||
const { isLoading, itemTitle, okIcon, cancelIcon, renameTitle, onKeyUpUpdateItem, onClickUpdateItem, cancelUpdateItem } = props;
|
||||
|
||||
return(
|
||||
<EditingWrapper>
|
||||
<TextInput
|
||||
className='edit-text'
|
||||
name='title'
|
||||
scale={true}
|
||||
value={itemTitle}
|
||||
tabIndex={1}
|
||||
isAutoFocussed={true}
|
||||
onChange={renameTitle}
|
||||
onKeyUp={onKeyUpUpdateItem}
|
||||
isDisabled={isLoading}
|
||||
/>
|
||||
<Button
|
||||
className='edit-button'
|
||||
size='medium'
|
||||
isDisabled={isLoading}
|
||||
onClick={onClickUpdateItem}
|
||||
icon={okIcon}
|
||||
/>
|
||||
<Button
|
||||
className='edit-button'
|
||||
size='medium'
|
||||
isDisabled={isLoading}
|
||||
onClick={cancelUpdateItem}
|
||||
icon={cancelIcon}
|
||||
/>
|
||||
</EditingWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(EditingWrapperComponent);
|
@ -1,16 +1,12 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { constants } from 'asc-web-common';
|
||||
import { EmptyScreenContainer, Link } from "asc-web-components";
|
||||
import { fetchFiles } from "../../../../../store/files/actions";
|
||||
import store from "../../../../../store/store";
|
||||
import { string } from "prop-types";
|
||||
|
||||
const { FileAction } = constants;
|
||||
import { EmptyScreenContainer } from "asc-web-components";
|
||||
|
||||
const EmptyFolderWrapper = styled.div`
|
||||
.empty-folder_container {
|
||||
max-width: 550px;
|
||||
.ec-image {
|
||||
padding-right: 16px;
|
||||
margin: 0 0 0 auto;
|
||||
}
|
||||
|
||||
.empty-folder_link {
|
||||
@ -30,91 +26,29 @@ const EmptyFolderWrapper = styled.div`
|
||||
.empty-folder_container_plus-image {
|
||||
margin: -8px 8px 0 0;
|
||||
}
|
||||
|
||||
.empty-folder_container-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const EmptyFolderContainer = props => {
|
||||
const linkStyles = {
|
||||
isHovered: true,
|
||||
type: "action",
|
||||
fontSize: "14px",
|
||||
className: "empty-folder_link",
|
||||
display: "flex"
|
||||
};
|
||||
|
||||
const onCreate = (format) => {
|
||||
props.setAction(
|
||||
{
|
||||
type: FileAction.Create,
|
||||
extension: format,
|
||||
id: -1
|
||||
});
|
||||
}
|
||||
|
||||
const onBackToParentFolder = () => {
|
||||
const newFilter = props.filter.clone();
|
||||
fetchFiles(props.parentId, newFilter, store.dispatch);
|
||||
};
|
||||
const EmptyFoldersContainer = props => {
|
||||
const imageAlt = "Empty folder image";
|
||||
|
||||
return (
|
||||
<EmptyFolderWrapper>
|
||||
<EmptyScreenContainer
|
||||
className="empty-folder_container"
|
||||
imageSrc="images/empty_screen.png"
|
||||
imageAlt="Empty folder image"
|
||||
headerText="В этой папке нет файлов"
|
||||
buttons={
|
||||
<>
|
||||
<div className="empty-folder_container-links">
|
||||
<Link
|
||||
className="empty-folder_container_plus-image"
|
||||
color="#83888d"
|
||||
fontSize="26px"
|
||||
fontWeight="800"
|
||||
noHover
|
||||
onClick={() => console.log("Create document click")}
|
||||
>
|
||||
+
|
||||
</Link>
|
||||
<Link onClick={onCreate.bind(this, 'docx')} {...linkStyles}>
|
||||
Документ,
|
||||
</Link>
|
||||
<Link onClick={onCreate.bind(this, 'xlsx')} {...linkStyles}>
|
||||
Таблица,
|
||||
</Link>
|
||||
<Link onClick={onCreate.bind(this, 'pptx')} {...linkStyles}>
|
||||
Презентация
|
||||
</Link>
|
||||
</div>
|
||||
<div className="empty-folder_container-links">
|
||||
<Link
|
||||
className="empty-folder_container_plus-image"
|
||||
color="#83888d"
|
||||
fontSize="26px"
|
||||
fontWeight="800"
|
||||
onClick={onCreate.bind(this, null)}
|
||||
noHover
|
||||
>
|
||||
+
|
||||
</Link>
|
||||
<Link {...linkStyles} onClick={onCreate.bind(this, null)}>
|
||||
Папка
|
||||
</Link>
|
||||
</div>
|
||||
<div className="empty-folder_container-links">
|
||||
<img
|
||||
className="empty-folder_container_up-image"
|
||||
src="images/up.svg"
|
||||
onClick={onBackToParentFolder}
|
||||
/>
|
||||
<Link onClick={onBackToParentFolder} {...linkStyles}>
|
||||
Вернутся в папку на уровень выше
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
imageSrc={props.imageSrc}
|
||||
imageAlt={imageAlt}
|
||||
headerText={props.headerText}
|
||||
subheadingText={props.subheadingText}
|
||||
descriptionText={props.descriptionText}
|
||||
buttons={props.buttons}
|
||||
/>
|
||||
</EmptyFolderWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmptyFolderContainer;
|
||||
export default EmptyFoldersContainer;
|
||||
|
@ -4,11 +4,12 @@ import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { RowContent, Link, Text, Icons, Badge, TextInput, Button, toastr } from "asc-web-components";
|
||||
import { RowContent, Link, Text, Icons, Badge, toastr } from "asc-web-components";
|
||||
import { constants } from 'asc-web-common';
|
||||
import { createFile, createFolder, renameFolder, updateFile, setFilter, fetchFiles } from '../../../../../store/files/actions';
|
||||
import { canWebEdit, canConvert, getTitleWithoutExst } from '../../../../../store/files/selectors';
|
||||
import store from "../../../../../store/store";
|
||||
import EditingWrapperComponent from "./EditingWrapperComponent";
|
||||
|
||||
const { FileAction } = constants;
|
||||
|
||||
@ -24,38 +25,38 @@ class FilesRowContent extends React.PureComponent {
|
||||
|
||||
this.state = {
|
||||
itemTitle: titleWithoutExt,
|
||||
editingId: props.fileAction.id,
|
||||
loading: false
|
||||
editingId: props.fileAction.id
|
||||
//loading: false
|
||||
};
|
||||
}
|
||||
|
||||
completeAction = () => {
|
||||
this.setState({ loading: false }, () =>
|
||||
this.props.onEditComplete());
|
||||
//this.setState({ loading: false }, () =>)
|
||||
this.props.onEditComplete();
|
||||
}
|
||||
|
||||
updateItem = () => {
|
||||
const { fileAction, updateFile, renameFolder, item } = this.props;
|
||||
const { fileAction, updateFile, renameFolder, item, onLoading } = this.props;
|
||||
|
||||
const { itemTitle } = this.state;
|
||||
const originalTitle = getTitleWithoutExst(item);
|
||||
|
||||
this.setState({ loading: true });
|
||||
|
||||
onLoading(true);
|
||||
if (originalTitle === itemTitle)
|
||||
return this.completeAction();
|
||||
|
||||
item.fileExst
|
||||
? updateFile(fileAction.id, itemTitle)
|
||||
.then(() => this.completeAction())
|
||||
.then(() => this.completeAction()).finally(() => onLoading(false))
|
||||
: renameFolder(fileAction.id, itemTitle)
|
||||
.then(() => this.completeAction());
|
||||
.then(() => this.completeAction()).finally(() => onLoading(false));
|
||||
};
|
||||
|
||||
createItem = () => {
|
||||
const { createFile, createFolder, item } = this.props;
|
||||
const { itemTitle } = this.state;
|
||||
|
||||
this.setState({ loading: true });
|
||||
//this.setState({ loading: true });
|
||||
|
||||
if (itemTitle.trim() === '')
|
||||
return this.completeAction();
|
||||
@ -81,7 +82,7 @@ class FilesRowContent extends React.PureComponent {
|
||||
}
|
||||
|
||||
cancelUpdateItem = () => {
|
||||
this.setState({ loading: false });
|
||||
//this.setState({ loading: false });
|
||||
this.completeAction();
|
||||
}
|
||||
|
||||
@ -160,8 +161,8 @@ class FilesRowContent extends React.PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, item, fileAction } = this.props;
|
||||
const { itemTitle, editingId, loading } = this.state;
|
||||
const { t, item, fileAction, isLoading, isTrashFolder } = this.props;
|
||||
const { itemTitle, editingId/*, loading*/ } = this.state;
|
||||
const {
|
||||
contentLength,
|
||||
updated,
|
||||
@ -195,42 +196,6 @@ class FilesRowContent extends React.PureComponent {
|
||||
}
|
||||
`;
|
||||
|
||||
const EditingWrapper = styled.div`
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
height: 56px;
|
||||
}
|
||||
.edit-text {
|
||||
height: 30px;
|
||||
font-size: 15px;
|
||||
outline: 0 !important;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
font-family: 'Open Sans',sans-serif,Arial;
|
||||
text-align: left;
|
||||
color: #333333;
|
||||
}
|
||||
.edit-button {
|
||||
margin-left: 8px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.edit-ok-icon {
|
||||
margin-top: -6px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.edit-cancel-icon {
|
||||
margin-top: -6px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
const titleWithoutExt = getTitleWithoutExst(item);
|
||||
const fileOwner = createdBy && ((this.props.viewer.id === createdBy.id && t("AuthorMe")) || createdBy.displayName);
|
||||
const updatedDate = updated && this.getStatusByDate();
|
||||
@ -252,35 +217,19 @@ class FilesRowContent extends React.PureComponent {
|
||||
/>;
|
||||
|
||||
const isEdit = (id === editingId) && (fileExst === fileAction.extension);
|
||||
const linkStyles = isTrashFolder ? { noHover: true } : { onClick: this.onFilesClick };
|
||||
|
||||
return isEdit
|
||||
? (<EditingWrapper>
|
||||
<TextInput
|
||||
className='edit-text'
|
||||
name='title'
|
||||
scale={true}
|
||||
value={itemTitle}
|
||||
tabIndex={1}
|
||||
isAutoFocussed={true}
|
||||
onChange={this.renameTitle}
|
||||
onKeyUp={this.onKeyUpUpdateItem}
|
||||
isDisabled={loading}
|
||||
? <EditingWrapperComponent
|
||||
isLoading={isLoading}
|
||||
itemTitle={itemTitle}
|
||||
okIcon={okIcon}
|
||||
cancelIcon={cancelIcon}
|
||||
renameTitle={this.renameTitle}
|
||||
onKeyUpUpdateItem={this.onKeyUpUpdateItem}
|
||||
onClickUpdateItem={this.onClickUpdateItem}
|
||||
cancelUpdateItem={this.cancelUpdateItem}
|
||||
/>
|
||||
<Button
|
||||
className='edit-button'
|
||||
size='medium'
|
||||
isDisabled={loading}
|
||||
onClick={this.onClickUpdateItem}
|
||||
icon={okIcon}
|
||||
/>
|
||||
<Button
|
||||
className='edit-button'
|
||||
size='medium'
|
||||
isDisabled={loading}
|
||||
onClick={this.cancelUpdateItem}
|
||||
icon={cancelIcon}
|
||||
/>
|
||||
</EditingWrapper>)
|
||||
: (
|
||||
<SimpleFilesRowContent
|
||||
sideColor="#333"
|
||||
@ -292,10 +241,10 @@ class FilesRowContent extends React.PureComponent {
|
||||
type='page'
|
||||
title={titleWithoutExt}
|
||||
fontWeight="bold"
|
||||
onClick={this.onFilesClick}
|
||||
fontSize='15px'
|
||||
{...linkStyles}
|
||||
color="#333"
|
||||
isTextOverflow={true}
|
||||
isTextOverflow
|
||||
>
|
||||
{titleWithoutExt}
|
||||
</Link>
|
||||
@ -421,10 +370,14 @@ class FilesRowContent extends React.PureComponent {
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const { filter, fileAction, selectedFolder, treeFolders } = state.files;
|
||||
const indexOfTrash = 3;
|
||||
|
||||
return {
|
||||
filter: state.files.filter,
|
||||
fileAction: state.files.fileAction,
|
||||
parentFolder: state.files.selectedFolder.id
|
||||
filter,
|
||||
fileAction,
|
||||
parentFolder: selectedFolder.id,
|
||||
isTrashFolder: treeFolders[indexOfTrash].id === selectedFolder.id
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,10 +6,11 @@ import { withTranslation } from "react-i18next";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import styled from "styled-components";
|
||||
import {
|
||||
Icons,
|
||||
IconButton,
|
||||
Row,
|
||||
RowContainer,
|
||||
toastr
|
||||
toastr,
|
||||
Link
|
||||
} from "asc-web-components";
|
||||
import EmptyFolderContainer from "./EmptyFolderContainer";
|
||||
import FilesRowContent from "./FilesRowContent";
|
||||
@ -25,14 +26,16 @@ import {
|
||||
setAction,
|
||||
setTreeFolders
|
||||
} from '../../../../../store/files/actions';
|
||||
import { isFileSelected, getFileIcon, getFolderIcon } from '../../../../../store/files/selectors';
|
||||
import { isFileSelected, getFileIcon, getFolderIcon, getFolderType, loopTreeFolders } from '../../../../../store/files/selectors';
|
||||
import store from "../../../../../store/store";
|
||||
//import { getFilterByLocation } from "../../../../../helpers/converters";
|
||||
//import config from "../../../../../../package.json";
|
||||
|
||||
//const { FilesFilter } = api;
|
||||
const { FilesFilter } = api;
|
||||
const { FileAction } = constants;
|
||||
|
||||
const linkStyles = { isHovered: true, type: "action", fontSize: "14px", className: "empty-folder_link", display: "flex" };
|
||||
|
||||
class SectionBodyContent extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -84,11 +87,12 @@ class SectionBodyContent extends React.PureComponent {
|
||||
if (fileAction.type === FileAction.Create || fileAction.type === FileAction.Rename) {
|
||||
onLoading(true);
|
||||
fetchFiles(folderId, filter, store.dispatch).then(data => {
|
||||
const newItem = item.id === -1 ? null : item;
|
||||
if (!item.fileExst) {
|
||||
const path = data.selectedFolder.pathParts;
|
||||
const newTreeFolders = treeFolders;
|
||||
const folders = data.selectedFolder.folders;
|
||||
this.loop(path, newTreeFolders, item.parentId, folders);
|
||||
loopTreeFolders(path, newTreeFolders, folders, null, newItem);
|
||||
setTreeFolders(newTreeFolders);
|
||||
}
|
||||
}).finally(() => onLoading(false))
|
||||
@ -108,76 +112,97 @@ class SectionBodyContent extends React.PureComponent {
|
||||
}
|
||||
|
||||
onDeleteFile = (fileId, currentFolderId) => {
|
||||
const { deleteFile, filter } = this.props;
|
||||
const { deleteFile, filter, onLoading } = this.props;
|
||||
|
||||
onLoading(true);
|
||||
deleteFile(fileId)
|
||||
.catch(err => toastr.error(err))
|
||||
.then(() => fetchFiles(currentFolderId, filter, store.dispatch))
|
||||
.then(() => toastr.success(`File moved to recycle bin`));
|
||||
.then(() => toastr.success(`File moved to recycle bin`))
|
||||
.finally(() => onLoading(false));
|
||||
}
|
||||
|
||||
loop = (path, item, folderId, folders, foldersCount) => {
|
||||
const newPath = path;
|
||||
while (path.length !== 0) {
|
||||
const newItems = item.find(x => x.id === path[0]);
|
||||
newPath.shift();
|
||||
if (path.length === 0) {
|
||||
const currentItem = item.find(x => x.id === folderId);
|
||||
currentItem.folders = folders;
|
||||
currentItem.foldersCount = foldersCount;
|
||||
return;
|
||||
}
|
||||
this.loop(newPath, newItems.folders, folderId, folders, foldersCount);
|
||||
}
|
||||
};
|
||||
|
||||
onDeleteFolder = (folderId, currentFolderId) => {
|
||||
const { deleteFolder, filter, treeFolders, setTreeFolders, onLoading } = this.props;
|
||||
const { deleteFolder, filter, treeFolders, setTreeFolders, onLoading, currentFolderType } = this.props;
|
||||
onLoading(true);
|
||||
deleteFolder(folderId, currentFolderId)
|
||||
.catch(err => toastr.error(err))
|
||||
.then(() =>
|
||||
fetchFiles(currentFolderId, filter, store.dispatch).then(data => {
|
||||
if(currentFolderType !== "Trash") {
|
||||
const path = data.selectedFolder.pathParts;
|
||||
const newTreeFolders = treeFolders;
|
||||
const folders = data.selectedFolder.folders;
|
||||
const foldersCount = data.selectedFolder.foldersCount;
|
||||
this.loop(path, newTreeFolders, currentFolderId, folders, foldersCount);
|
||||
loopTreeFolders(path, newTreeFolders, folders, foldersCount);
|
||||
setTreeFolders(newTreeFolders);
|
||||
}
|
||||
})
|
||||
)
|
||||
.then(() => toastr.success(`Folder moved to recycle bin`))
|
||||
.finally(() => onLoading(false));
|
||||
}
|
||||
|
||||
onClickLinkForPortal = (folderId) => {
|
||||
return fetchFolder(folderId, store.dispatch);
|
||||
onClickShare = item => {
|
||||
return false;
|
||||
}
|
||||
|
||||
onClickLinkForPortal = item => {
|
||||
return fetchFolder(item.folderId, store.dispatch);
|
||||
}
|
||||
|
||||
onClickDownload = item => {
|
||||
return window.open(item.viewUrl, "_blank");
|
||||
}
|
||||
|
||||
onClickLinkEdit = item => {
|
||||
return window.open(`./doceditor?fileId=${item.id}`, "_blank");
|
||||
}
|
||||
|
||||
getFilesContextOptions = (item, viewer) => {
|
||||
return [
|
||||
const isFile = !!item.fileExst;
|
||||
|
||||
const menu = [
|
||||
{
|
||||
key: "sharing-settings",
|
||||
label: "Sharing settings",
|
||||
onClick: () => { },
|
||||
onClick: this.onClickShare.bind(this, item),
|
||||
disabled: true
|
||||
},
|
||||
isFile
|
||||
? {
|
||||
key: "send-by-email",
|
||||
label: "Send by e-mail",
|
||||
onClick: () => { },
|
||||
disabled: true
|
||||
}
|
||||
: null,
|
||||
{
|
||||
key: "link-for-portal-users",
|
||||
label: "Link for portal users",
|
||||
onClick: this.onClickLinkForPortal.bind(this, item.folderId),
|
||||
onClick: this.onClickLinkForPortal.bind(this, item),
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
key: "sep",
|
||||
isSeparator: true
|
||||
},
|
||||
{
|
||||
isFile
|
||||
? {
|
||||
key: "edit",
|
||||
label: "Edit",
|
||||
onClick: this.onClickLinkEdit.bind(this, item),
|
||||
disabled: false
|
||||
}
|
||||
: null,
|
||||
isFile
|
||||
? {
|
||||
key: "download",
|
||||
label: "Download",
|
||||
onClick: () => { },
|
||||
disabled: true
|
||||
},
|
||||
onClick: this.onClickDownload.bind(this, item),
|
||||
disabled: false
|
||||
}
|
||||
: null,
|
||||
{
|
||||
key: "rename",
|
||||
label: "Rename",
|
||||
@ -190,7 +215,9 @@ class SectionBodyContent extends React.PureComponent {
|
||||
onClick: this.onClickDelete.bind(this, item),
|
||||
disabled: false
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
return menu;
|
||||
};
|
||||
|
||||
needForUpdate = (currentProps, nextProps) => {
|
||||
@ -222,6 +249,7 @@ class SectionBodyContent extends React.PureComponent {
|
||||
const icon = extension
|
||||
? getFileIcon(extension, 24)
|
||||
: getFolderIcon(item.providerKey, 24);
|
||||
const loader = <div style={{width: '24px'}}></div>
|
||||
|
||||
return <ReactSVG
|
||||
beforeInjection={svg => {
|
||||
@ -229,17 +257,270 @@ class SectionBodyContent extends React.PureComponent {
|
||||
isEdit && svg.setAttribute('style', 'margin-left: 24px');
|
||||
}}
|
||||
src={icon}
|
||||
loading={() => loader}
|
||||
/>;
|
||||
};
|
||||
|
||||
onCreate = (format) => {
|
||||
this.props.setAction({
|
||||
type: FileAction.Create,
|
||||
extension: format,
|
||||
id: -1,
|
||||
});
|
||||
};
|
||||
|
||||
onResetFilter = () => {
|
||||
const { selectedFolderId, onLoading } = this.props;
|
||||
onLoading(true);
|
||||
const newFilter = FilesFilter.getDefault();
|
||||
fetchFiles(selectedFolderId, newFilter, store.dispatch).catch(err =>
|
||||
toastr.error(err)
|
||||
).finally(() => onLoading(false));
|
||||
}
|
||||
|
||||
onGoToMyDocuments = () => {
|
||||
const { filter, myDocumentsId, onLoading } = this.props;
|
||||
const newFilter = filter.clone();
|
||||
onLoading(true);
|
||||
fetchFiles(myDocumentsId, newFilter, store.dispatch).finally(() =>
|
||||
onLoading(false)
|
||||
);
|
||||
};
|
||||
|
||||
onBackToParentFolder = () => {
|
||||
const { filter, parentId, onLoading } = this.props;
|
||||
const newFilter = filter.clone();
|
||||
onLoading(true);
|
||||
fetchFiles(parentId, newFilter, store.dispatch).finally(() =>
|
||||
onLoading(false)
|
||||
);
|
||||
};
|
||||
|
||||
renderEmptyRootFolderContainer = () => {
|
||||
const { currentFolderType, title, t } = this.props;
|
||||
const subheadingText = t("SubheadingEmptyText");
|
||||
const myDescription = t("MyEmptyContainerDescription");
|
||||
const shareDescription = t("SharedEmptyContainerDescription");
|
||||
const commonDescription = t("CommonEmptyContainerDescription");
|
||||
const trashDescription = t("TrashEmptyContainerDescription");
|
||||
|
||||
const commonButtons = (
|
||||
<>
|
||||
<div className="empty-folder_container-links">
|
||||
<Link
|
||||
className="empty-folder_container_plus-image"
|
||||
color="#83888d"
|
||||
fontSize="26px"
|
||||
fontWeight="800"
|
||||
noHover
|
||||
onClick={this.onCreate.bind(this, "docx")}
|
||||
>
|
||||
+
|
||||
</Link>
|
||||
<Link onClick={this.onCreate.bind(this, "docx")} {...linkStyles}>
|
||||
{t("Document")},
|
||||
</Link>
|
||||
<Link onClick={this.onCreate.bind(this, "xlsx")} {...linkStyles}>
|
||||
{t("Spreadsheet")},
|
||||
</Link>
|
||||
<Link onClick={this.onCreate.bind(this, "pptx")} {...linkStyles}>
|
||||
{t("Presentation")}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="empty-folder_container-links">
|
||||
<Link
|
||||
className="empty-folder_container_plus-image"
|
||||
color="#83888d"
|
||||
fontSize="26px"
|
||||
fontWeight="800"
|
||||
onClick={this.onCreate.bind(this, null)}
|
||||
noHover
|
||||
>
|
||||
+
|
||||
</Link>
|
||||
<Link {...linkStyles} onClick={this.onCreate.bind(this, null)}>
|
||||
{t("Folder")}
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const trashButtons = (
|
||||
<div className="empty-folder_container-links">
|
||||
<img
|
||||
className="empty-folder_container_up-image"
|
||||
src="images/empty_screen_people.svg"
|
||||
alt=""
|
||||
onClick={this.onGoToMyDocuments}
|
||||
/>
|
||||
<Link onClick={this.onGoToMyDocuments} {...linkStyles}>
|
||||
{t("GoToMyButton")}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
switch (currentFolderType) {
|
||||
case "My":
|
||||
return (
|
||||
<EmptyFolderContainer
|
||||
headerText={title}
|
||||
subheadingText={subheadingText}
|
||||
descriptionText={myDescription}
|
||||
imageSrc="images/empty_screen.png"
|
||||
buttons={commonButtons}
|
||||
/>
|
||||
);
|
||||
case "Share":
|
||||
return (
|
||||
<EmptyFolderContainer
|
||||
headerText={title}
|
||||
subheadingText={subheadingText}
|
||||
descriptionText={shareDescription}
|
||||
imageSrc="images/empty_screen_forme.png"
|
||||
/>
|
||||
);
|
||||
case "Common":
|
||||
return (
|
||||
<EmptyFolderContainer
|
||||
headerText={title}
|
||||
subheadingText={subheadingText}
|
||||
descriptionText={commonDescription}
|
||||
imageSrc="images/empty_screen_corporate.png"
|
||||
buttons={commonButtons}
|
||||
/>
|
||||
);
|
||||
case "Trash":
|
||||
return (
|
||||
<EmptyFolderContainer
|
||||
headerText={title}
|
||||
subheadingText={subheadingText}
|
||||
descriptionText={trashDescription}
|
||||
imageSrc="images/empty_screen_trash.png"
|
||||
buttons={trashButtons}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
renderEmptyFolderContainer = () => {
|
||||
const { t } = this.props;
|
||||
const buttons = (
|
||||
<>
|
||||
<div className="empty-folder_container-links">
|
||||
<Link
|
||||
className="empty-folder_container_plus-image"
|
||||
color="#83888d"
|
||||
fontSize="26px"
|
||||
fontWeight="800"
|
||||
noHover
|
||||
onClick={() => console.log("Create document click")}
|
||||
>
|
||||
+
|
||||
</Link>
|
||||
<Link onClick={this.onCreate.bind(this, "docx")} {...linkStyles}>
|
||||
{t("Document")},
|
||||
</Link>
|
||||
<Link onClick={this.onCreate.bind(this, "xlsx")} {...linkStyles}>
|
||||
{t("Spreadsheet")},
|
||||
</Link>
|
||||
<Link onClick={this.onCreate.bind(this, "pptx")} {...linkStyles}>
|
||||
{t("Presentation")}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="empty-folder_container-links">
|
||||
<Link
|
||||
className="empty-folder_container_plus-image"
|
||||
color="#83888d"
|
||||
fontSize="26px"
|
||||
fontWeight="800"
|
||||
onClick={this.onCreate.bind(this, null)}
|
||||
noHover
|
||||
>
|
||||
+
|
||||
</Link>
|
||||
<Link {...linkStyles} onClick={this.onCreate.bind(this, null)}>
|
||||
{t("Folder")}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="empty-folder_container-links">
|
||||
<img
|
||||
className="empty-folder_container_up-image"
|
||||
src="images/up.svg"
|
||||
onClick={this.onBackToParentFolder}
|
||||
alt=""
|
||||
/>
|
||||
<Link onClick={this.onBackToParentFolder} {...linkStyles}>
|
||||
{t("BackToParentFolderButton")}
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<EmptyFolderContainer
|
||||
headerText={t("EmptyFolderHeader")}
|
||||
imageSrc="images/empty_screen.png"
|
||||
buttons={buttons}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderEmptyFilterContainer = () => {
|
||||
const { t } = this.props;
|
||||
const subheadingText = t("EmptyFilterSubheadingText");
|
||||
const descriptionText = t("EmptyFilterDescriptionText");
|
||||
|
||||
const buttons = (
|
||||
<div className="empty-folder_container-links">
|
||||
<IconButton
|
||||
className="empty-folder_container-icon"
|
||||
size="12"
|
||||
onClick={this.onResetFilter}
|
||||
iconName="CrossIcon"
|
||||
isFill
|
||||
color="A3A9AE"
|
||||
/>
|
||||
<Link onClick={this.onResetFilter} {...linkStyles}>
|
||||
{this.props.t("ClearButton")}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<EmptyFolderContainer
|
||||
headerText={t("Filter")}
|
||||
subheadingText={subheadingText}
|
||||
descriptionText={descriptionText}
|
||||
imageSrc="images/empty_screen_filter.png"
|
||||
buttons={buttons}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { files, folders, viewer, parentId, folderId, settings, selection, fileAction, onLoading, filter } = this.props;
|
||||
const {
|
||||
files,
|
||||
folders,
|
||||
viewer,
|
||||
parentId,
|
||||
folderId,
|
||||
settings,
|
||||
selection,
|
||||
fileAction,
|
||||
onLoading,
|
||||
isLoading,
|
||||
currentFolderCount,
|
||||
} = this.props;
|
||||
const { editingId } = this.state;
|
||||
|
||||
let items = [...folders, ...files];
|
||||
|
||||
const SimpleFilesRow = styled(Row)`
|
||||
${props => !props.contextOptions && `
|
||||
${(props) =>
|
||||
!props.contextOptions &&
|
||||
`
|
||||
& > div:last-child {
|
||||
width: 0px;
|
||||
}
|
||||
@ -249,20 +530,35 @@ class SectionBodyContent extends React.PureComponent {
|
||||
if (fileAction && fileAction.type === FileAction.Create) {
|
||||
items.unshift({
|
||||
id: -1,
|
||||
title: '',
|
||||
title: "",
|
||||
parentId: folderId,
|
||||
fileExst: fileAction.extension
|
||||
})
|
||||
fileExst: fileAction.extension,
|
||||
});
|
||||
}
|
||||
|
||||
return items.length > 0 ? (
|
||||
|
||||
|
||||
return !fileAction.id && currentFolderCount === 0 ? (
|
||||
parentId === 0 ? (
|
||||
this.renderEmptyRootFolderContainer()
|
||||
) : (
|
||||
this.renderEmptyFolderContainer()
|
||||
)
|
||||
) : !fileAction.id && items.length === 0 ? (
|
||||
this.renderEmptyFilterContainer()
|
||||
) : (
|
||||
<RowContainer useReactWindow={false}>
|
||||
{items.map(item => {
|
||||
const isEdit = fileAction.type && (editingId === item.id || item.id === -1) && (item.fileExst === fileAction.extension);
|
||||
const contextOptions = this.getFilesContextOptions(item, viewer).filter(o => o);
|
||||
const contextOptionsProps = !contextOptions.length || isEdit
|
||||
? {}
|
||||
: { contextOptions };
|
||||
{items.map((item) => {
|
||||
const isEdit =
|
||||
fileAction.type &&
|
||||
(editingId === item.id || item.id === -1) &&
|
||||
item.fileExst === fileAction.extension;
|
||||
const contextOptions = this.getFilesContextOptions(
|
||||
item,
|
||||
viewer
|
||||
).filter((o) => o);
|
||||
const contextOptionsProps =
|
||||
!contextOptions.length || isEdit ? {} : { contextOptions };
|
||||
const checked = isFileSelected(selection, item.id, item.parentId);
|
||||
const checkedProps = /* isAdmin(viewer) */ isEdit ? {} : { checked };
|
||||
const element = this.getItemIcon(item, isEdit);
|
||||
@ -278,14 +574,19 @@ class SectionBodyContent extends React.PureComponent {
|
||||
{...contextOptionsProps}
|
||||
needForUpdate={this.needForUpdate}
|
||||
>
|
||||
<FilesRowContent item={item} viewer={viewer} culture={settings.culture} onEditComplete={this.onEditComplete.bind(this, item)} onLoading={onLoading} />
|
||||
<FilesRowContent
|
||||
item={item}
|
||||
viewer={viewer}
|
||||
culture={settings.culture}
|
||||
onEditComplete={this.onEditComplete.bind(this, item)}
|
||||
onLoading={onLoading}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</SimpleFilesRow>
|
||||
);
|
||||
})}
|
||||
</RowContainer>
|
||||
) : parentId !== 0 ? (
|
||||
<EmptyFolderContainer parentId={parentId} filter={filter} setAction={this.props.setAction} />
|
||||
) : <p>RootFolderContainer</p>;
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,6 +595,13 @@ SectionBodyContent.defaultProps = {
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { selectedFolder, treeFolders } = state.files;
|
||||
const { id, title, foldersCount, filesCount } = selectedFolder;
|
||||
const currentFolderType = getFolderType(id, treeFolders);
|
||||
|
||||
const myFolderIndex = 0;
|
||||
const currentFolderCount = filesCount + foldersCount;
|
||||
|
||||
return {
|
||||
fileAction: state.files.fileAction,
|
||||
files: state.files.files,
|
||||
@ -305,7 +613,12 @@ const mapStateToProps = state => {
|
||||
selection: state.files.selection,
|
||||
settings: state.auth.settings,
|
||||
viewer: state.auth.user,
|
||||
treeFolders: state.files.treeFolders
|
||||
treeFolders: state.files.treeFolders,
|
||||
currentFolderType,
|
||||
title,
|
||||
myDocumentsId: treeFolders[myFolderIndex].id,
|
||||
currentFolderCount,
|
||||
selectedFolderId: id
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -17,7 +17,8 @@ import { EmptyTrashDialog, DeleteDialog } from "../../../../dialogs";
|
||||
import { SharingPanel } from "../../../../panels";
|
||||
import {
|
||||
isCanBeDeleted,
|
||||
getAccessOption
|
||||
getAccessOption,
|
||||
checkFolderType
|
||||
} from "../../../../../store/files/selectors";
|
||||
|
||||
const { isAdmin } = store.auth.selectors;
|
||||
@ -233,7 +234,11 @@ class SectionHeaderContent extends React.Component {
|
||||
};
|
||||
|
||||
onBackToParentFolder = () => {
|
||||
fetchFiles(this.props.parentId, this.props.filter, filesStore.dispatch);
|
||||
const { onLoading, parentId, filter } = this.props;
|
||||
onLoading(true);
|
||||
fetchFiles(parentId, filter, filesStore.dispatch).finally(() =>
|
||||
onLoading(false)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -255,7 +260,8 @@ class SectionHeaderContent extends React.Component {
|
||||
onCheck,
|
||||
title,
|
||||
accessOptions,
|
||||
shareDataItems
|
||||
shareDataItems,
|
||||
currentFolderId
|
||||
} = this.props;
|
||||
const {
|
||||
showDeleteDialog,
|
||||
@ -416,6 +422,7 @@ class SectionHeaderContent extends React.Component {
|
||||
|
||||
{showEmptyTrashDialog && (
|
||||
<EmptyTrashDialog
|
||||
currentFolderId={currentFolderId}
|
||||
visible={showEmptyTrashDialog}
|
||||
onClose={this.onEmptyTrashAction}
|
||||
/>
|
||||
@ -448,14 +455,15 @@ const mapStateToProps = state => {
|
||||
return {
|
||||
folder: parentId !== 0,
|
||||
isAdmin: isAdmin(user),
|
||||
isRecycleBinFolder: treeFolders[indexOfTrash].id === id,
|
||||
isRecycleBinFolder: checkFolderType(id, indexOfTrash, treeFolders),
|
||||
parentId,
|
||||
selection,
|
||||
title,
|
||||
filter,
|
||||
deleteDialogVisible: isCanBeDeleted(selectedFolder, user),
|
||||
accessOptions: getAccessOption(selection),
|
||||
shareDataItems
|
||||
shareDataItems,
|
||||
currentFolderId: id
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -119,7 +119,7 @@ const SectionPagingContent = ({
|
||||
const selectedPageItem = pageItems.find(x => x.key === filter.page) || emptyPageSelection;
|
||||
const selectedCountItem = countItems.find(x => x.key === filter.pageCount) || emptyCountSelection;
|
||||
|
||||
console.log("SectionPagingContent render", filter);
|
||||
//console.log("SectionPagingContent render", filter);
|
||||
|
||||
return filter.total < filter.pageCount ? (
|
||||
<></>
|
||||
|
@ -2,7 +2,7 @@ import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
import { withRouter } from "react-router";
|
||||
import { RequestLoader } from "asc-web-components";
|
||||
import { RequestLoader, Checkbox } from "asc-web-components";
|
||||
import { PageLayout, utils } from "asc-web-common";
|
||||
import { withTranslation, I18nextProvider } from 'react-i18next';
|
||||
import i18n from "./i18n";
|
||||
@ -29,7 +29,14 @@ class PureHome extends React.Component {
|
||||
isHeaderVisible: false,
|
||||
isHeaderIndeterminate: false,
|
||||
isHeaderChecked: false,
|
||||
isLoading: false
|
||||
isLoading: false,
|
||||
|
||||
showProgressBar: false,
|
||||
progressBarValue: 0,
|
||||
progressBarLabel: "",
|
||||
overwriteSetting: false,
|
||||
uploadOriginalFormatSetting: false,
|
||||
hideWindowSetting: false
|
||||
};
|
||||
}
|
||||
|
||||
@ -93,15 +100,53 @@ class PureHome extends React.Component {
|
||||
this.setState({ isLoading: status });
|
||||
};
|
||||
|
||||
setProgressVisible = visible => {
|
||||
if(visible) {this.setState({ showProgressBar: visible })}
|
||||
else { setTimeout(() => this.setState({ showProgressBar: visible, progressBarValue: 0 }), 10000)};
|
||||
};
|
||||
setProgressValue = value => this.setState({ progressBarValue: value });
|
||||
setProgressLabel = label => this.setState({ progressBarLabel: label });
|
||||
|
||||
onChangeOverwrite = () => this.setState({overwriteSetting: !this.state.overwriteSetting})
|
||||
onChangeOriginalFormat = () => this.setState({uploadOriginalFormatSetting: !this.state.uploadOriginalFormatSetting})
|
||||
onChangeWindowVisible = () => this.setState({hideWindowSetting: !this.state.hideWindowSetting})
|
||||
|
||||
render() {
|
||||
const {
|
||||
isHeaderVisible,
|
||||
isHeaderIndeterminate,
|
||||
isHeaderChecked,
|
||||
selected,
|
||||
isLoading
|
||||
isLoading,
|
||||
showProgressBar,
|
||||
progressBarValue,
|
||||
progressBarLabel,
|
||||
overwriteSetting,
|
||||
uploadOriginalFormatSetting,
|
||||
hideWindowSetting
|
||||
} = this.state;
|
||||
const { t } = this.props;
|
||||
|
||||
const progressBarContent = (
|
||||
<div>
|
||||
<Checkbox
|
||||
onChange={this.onChangeOverwrite}
|
||||
isChecked={overwriteSetting}
|
||||
label={t("OverwriteSetting")}
|
||||
/>
|
||||
<Checkbox
|
||||
onChange={this.onChangeOriginalFormat}
|
||||
isChecked={uploadOriginalFormatSetting}
|
||||
label={t("UploadOriginalFormatSetting")}
|
||||
/>
|
||||
<Checkbox
|
||||
onChange={this.onChangeWindowVisible}
|
||||
isChecked={hideWindowSetting}
|
||||
label={t("HideWindowSetting")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<RequestLoader
|
||||
@ -116,15 +161,19 @@ class PureHome extends React.Component {
|
||||
<PageLayout
|
||||
withBodyScroll
|
||||
withBodyAutoFocus
|
||||
|
||||
//showProgressBar
|
||||
//progressBarMaxValue
|
||||
//progressBarValue
|
||||
//progressBarDropDownContent
|
||||
//progressBarLabel={`Uploading files: ${progressBarValue} of ${progressBarMaxValue}`}
|
||||
|
||||
showProgressBar={showProgressBar}
|
||||
progressBarValue={progressBarValue}
|
||||
progressBarDropDownContent={progressBarContent}
|
||||
progressBarLabel={progressBarLabel}
|
||||
articleHeaderContent={<ArticleHeaderContent />}
|
||||
articleMainButtonContent={<ArticleMainButtonContent/>}
|
||||
articleMainButtonContent={
|
||||
<ArticleMainButtonContent
|
||||
onLoading={this.onLoading}
|
||||
setProgressVisible={this.setProgressVisible}
|
||||
setProgressValue={this.setProgressValue}
|
||||
setProgressContent={this.setProgressContent}
|
||||
setProgressLabel={this.setProgressLabel}
|
||||
/>}
|
||||
articleBodyContent={<ArticleBodyContent onLoading={this.onLoading} isLoading={isLoading} />}
|
||||
sectionHeaderContent={
|
||||
<SectionHeaderContent
|
||||
@ -141,6 +190,7 @@ class PureHome extends React.Component {
|
||||
sectionBodyContent={
|
||||
<SectionBodyContent
|
||||
selected={selected}
|
||||
isLoading={isLoading}
|
||||
onLoading={this.onLoading}
|
||||
onChange={this.onRowChange}
|
||||
/>
|
||||
|
@ -15,7 +15,10 @@
|
||||
"Author": "Author",
|
||||
"Search": "Search",
|
||||
"Folders": "Folders",
|
||||
"Document": "Document",
|
||||
"Documents": "Documents",
|
||||
"Spreadsheet": "Spreadsheet",
|
||||
"Presentation": "Presentation",
|
||||
"Presentations": "Presentations",
|
||||
"Spreadsheets": "Spreadsheets",
|
||||
"Images": "Images",
|
||||
@ -52,5 +55,21 @@
|
||||
"CloseButton": "Close",
|
||||
"All": "All",
|
||||
"Files": "Files",
|
||||
"EmptyRecycleBin": "Empty Recycle Bin"
|
||||
"EmptyRecycleBin": "Empty Recycle Bin",
|
||||
"Folder": "Folder",
|
||||
"ClearButton": "Reset filter",
|
||||
"SubheadingEmptyText": "No files to be displayed in this section",
|
||||
"MyEmptyContainerDescription": "The documents and image files you create or upload to the portal are kept here in 'My Documents' section. You can open and edit them using the ONLYOFFICE™ portal editor, share them with friends or colleagues, organize into folders. Drag-and-drop the files from your computer here to upload them to your portal even more easily.",
|
||||
"SharedEmptyContainerDescription": "The 'Shared with Me' section is used to show the files which your friends or colleagues gave you access to. In case you haven't seen the latest changes in the documents they are marked 'new'. You can remove the files from the list clicking the appropriate button.",
|
||||
"CommonEmptyContainerDescription": "The 'Common Documents' section shows all the documents shared by portal administrator for common access. Only portal administrator can create folders in this section, but with the access granted the portal users can also upload their files here. Drag-and-drop the files from your computer here to upload them to your portal even more easily.",
|
||||
"TrashEmptyContainerDescription": "The 'Recycle Bin' section is where all the deleted files are moved. You can either restore them in case they are deleted by mistake or delete them permanently. Please note, that when you delete the files from the 'Recycle Bin' they cannot be restored any longer.",
|
||||
"GoToMyButton": "Go to My Documents",
|
||||
"BackToParentFolderButton" : "Back to parent folder",
|
||||
"EmptyFolderHeader": "No files in this folder",
|
||||
"EmptyFilterSubheadingText": "No files to be displayed for this filter here",
|
||||
"EmptyFilterDescriptionText": "No files or folders matching your filter can be displayed in this section. Please select other filter options or clear filter to view all the files in this section. You can also look for the file you need in other sections.",
|
||||
"Filter": "Filter",
|
||||
"OverwriteSetting": "Overwrite existing file with the same name",
|
||||
"UploadOriginalFormatSetting": "Upload the documents in original format as well",
|
||||
"HideWindowSetting": "Show this window minimized"
|
||||
}
|
@ -15,7 +15,10 @@
|
||||
"Author": "Автор",
|
||||
"Search": "Поиск",
|
||||
"Folders": "Папки",
|
||||
"Document": "Документ",
|
||||
"Documents": "Документы",
|
||||
"Spreadsheet": "Таблица",
|
||||
"Presentation": "Презентация",
|
||||
"Presentations": "Презентации",
|
||||
"Spreadsheets": "Таблицы",
|
||||
"Images": "Изображения",
|
||||
@ -52,5 +55,21 @@
|
||||
"CloseButton": "Закрыть",
|
||||
"All": "Все",
|
||||
"Files": "Файлы",
|
||||
"EmptyRecycleBin": "Очистить корзину"
|
||||
"EmptyRecycleBin": "Очистить корзину",
|
||||
"Folder": "Папка",
|
||||
"ClearButton": "Сбросить фильтр",
|
||||
"SubheadingEmptyText": "Нет файлов для отображения в этом разделе",
|
||||
"MyEmptyContainerDescription": "Документы и файлы изображений, которые вы создаете или загружаете на портал, хранятся здесь, в разделе «Мои документы». Вы можете открывать и редактировать их с помощью редактора портала ONLYOFFICE ™, делиться ими с друзьями или коллегами, организовывать в папки. Перетащите файлы со своего компьютера сюда, чтобы загрузить их на свой портал еще проще.",
|
||||
"SharedEmptyContainerDescription": "Раздел «Доступно для меня» используется для отображения файлов, к которым ваши друзья или коллеги предоставили вам доступ. Если вы не видели последние изменения в документах, они помечаются как «новые». Вы можете удалить файлы из списка, нажав соответствующую кнопку.",
|
||||
"CommonEmptyContainerDescription": "В разделе «Общие документы» отображаются все документы, которыми администратор портала предоставил общий доступ. Только администраторы портала могут создавать папки в этом разделе, но с предоставленным доступом пользователи портала также могут загружать свои файлы здесь. Перетащите файлы со своего компьютера сюда, чтобы загрузить их на свой портал еще проще.",
|
||||
"TrashEmptyContainerDescription": "В разделе «Корзина» находятся все удаленные файлы. Вы можете восстановить их, если они были удалены по ошибке, или удалить их навсегда. Обратите внимание, что когда вы удаляете файлы из корзины, они больше не могут быть восстановлены.",
|
||||
"GoToMyButton": "Перейти к моим документам",
|
||||
"BackToParentFolderButton" : "Вернуться в папку на уровень выше",
|
||||
"EmptyFolderHeader": "В этой папке нет файлов",
|
||||
"EmptyFilterSubheadingText": "Здесь нет файлов, соответствующих этому фильтру",
|
||||
"EmptyFilterDescriptionText": "В этом разделе нет файлов или папок, соответствующих фильтру. Пожалуйста, выберите другие параметры или очистите фильтр, чтобы показать все файлы в этом разделе. Вы можете также поискать нужный файл в других разделах.",
|
||||
"Filter": "Фильтр",
|
||||
"OverwriteSetting": "Перезаписывать существующий файл с таким же именем",
|
||||
"UploadOriginalFormatSetting": "Сохранять также копию файла в исходном формате",
|
||||
"HideWindowSetting": "Показывать это окно минимизированным"
|
||||
}
|
@ -18,7 +18,7 @@ import {
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { utils as commonUtils, constants } from "asc-web-common";
|
||||
import { utils as commonUtils, constants, api } from "asc-web-common";
|
||||
import i18n from "./i18n";
|
||||
import {
|
||||
getShareUsersAndGroups,
|
||||
@ -27,6 +27,7 @@ import {
|
||||
setSharedFiles,
|
||||
setShareData,
|
||||
} from "../../../store/files/actions";
|
||||
import { checkFolderType } from "../../../store/files/selectors";
|
||||
import {
|
||||
StyledSharingPanel,
|
||||
StyledContent,
|
||||
@ -39,6 +40,7 @@ import SharingRow from "./SharingRow";
|
||||
|
||||
const { changeLanguage } = commonUtils;
|
||||
const { ShareAccessRights } = constants;
|
||||
const { files } = api;
|
||||
|
||||
class SharingPanelComponent extends React.Component {
|
||||
constructor(props) {
|
||||
@ -60,7 +62,7 @@ class SharingPanelComponent extends React.Component {
|
||||
rights: "ReadOnly",
|
||||
accessNumber: ShareAccessRights.ReadOnly,
|
||||
},
|
||||
shareLink: ""
|
||||
shareLink: "",
|
||||
};
|
||||
|
||||
this.ref = React.createRef();
|
||||
@ -74,7 +76,7 @@ class SharingPanelComponent extends React.Component {
|
||||
this.setState({ showActionPanel: !this.state.showActionPanel });
|
||||
};
|
||||
|
||||
onKeyClick = () => console.log("onKeyClick");
|
||||
//onKeyClick = () => console.log("onKeyClick");
|
||||
|
||||
onSaveClick = () => {
|
||||
toastr.success("onSaveClick");
|
||||
@ -272,14 +274,18 @@ class SharingPanelComponent extends React.Component {
|
||||
};
|
||||
|
||||
getItemAccess = (item) => {
|
||||
switch (item.access) {
|
||||
case 1:
|
||||
return {
|
||||
const fullAccessRights = {
|
||||
icon: "AccessEditIcon",
|
||||
rights: "FullAccess",
|
||||
accessNumber: ShareAccessRights.FullAccess,
|
||||
isOwner: item.isOwner,
|
||||
};
|
||||
if (item.sharedTo.shareLink) {
|
||||
return fullAccessRights;
|
||||
}
|
||||
switch (item.access) {
|
||||
case 1:
|
||||
return fullAccessRights;
|
||||
case 2:
|
||||
return {
|
||||
icon: "EyeIcon",
|
||||
@ -320,54 +326,75 @@ class SharingPanelComponent extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
getShareDataItems = (res, shareDataItems, foldersIds, filesIds) => {
|
||||
const listOwners = [];
|
||||
getShareDataItems = (items, shareDataItems, foldersIds, filesIds) => {
|
||||
const storeShareData = [];
|
||||
|
||||
for (let array of res) {
|
||||
let foldersIndex = 0;
|
||||
let filesIndex = 0;
|
||||
let shareIndex = 0;
|
||||
|
||||
if (foldersIds.length > foldersIndex) {
|
||||
storeShareData.push({
|
||||
folderId: foldersIds[foldersIndex],
|
||||
shareDataItems: [],
|
||||
});
|
||||
foldersIndex++;
|
||||
} else {
|
||||
storeShareData.push({
|
||||
fileId: filesIds[filesIndex],
|
||||
shareDataItems: [],
|
||||
});
|
||||
filesIndex++;
|
||||
}
|
||||
|
||||
let arrayItems = [];
|
||||
for (let item of array) {
|
||||
const arrayItems = [];
|
||||
for (let item of items) {
|
||||
const rights = this.getItemAccess(item);
|
||||
|
||||
if (rights) {
|
||||
item.sharedTo = { ...item.sharedTo, ...{ rights } };
|
||||
item.sharedTo.shareLink
|
||||
? shareDataItems.unshift(item.sharedTo)
|
||||
: shareDataItems.push(item.sharedTo);
|
||||
arrayItems.push(item.sharedTo);
|
||||
}
|
||||
}
|
||||
storeShareData[shareIndex].shareDataItems = arrayItems;
|
||||
arrayItems = [];
|
||||
shareIndex++;
|
||||
if (foldersIds) {
|
||||
storeShareData.push({
|
||||
folderId: foldersIds[0],
|
||||
shareDataItems: arrayItems,
|
||||
});
|
||||
} else {
|
||||
storeShareData.push({ fileId: filesIds[0], shareDataItems: arrayItems });
|
||||
}
|
||||
|
||||
for (let item of shareDataItems) {
|
||||
if (item.rights && item.rights.isOwner) {
|
||||
listOwners.push(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
this.props.setShareData([...storeShareData, ...this.props.shareData]);
|
||||
return [shareDataItems, listOwners];
|
||||
|
||||
let listOfArrays = [...shareDataItems.slice(0), ...[arrayItems.slice(0)]];
|
||||
|
||||
listOfArrays = listOfArrays.filter((x) => x.length !== 0);
|
||||
|
||||
let allItems = [];
|
||||
for (let array of listOfArrays) {
|
||||
for (let item of array) {
|
||||
allItems.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
allItems = this.removeDuplicateShareData(allItems);
|
||||
allItems = JSON.parse(JSON.stringify(allItems));
|
||||
|
||||
let stash = 0;
|
||||
for (let item of allItems) {
|
||||
let length = listOfArrays.length;
|
||||
if (!item.shareLink) {
|
||||
while (length !== 0) {
|
||||
if (listOfArrays[length - 1].length !== 0) {
|
||||
stash = listOfArrays[length - 1].find((x) => x.id === item.id);
|
||||
if (stash === this.props.isMyId) {
|
||||
const adminRights = {
|
||||
icon: "AccessEditIcon",
|
||||
rights: "FullAccess",
|
||||
accessNumber: ShareAccessRights.FullAccess,
|
||||
isOwner: item.isOwner,
|
||||
};
|
||||
item.rights = adminRights;
|
||||
} else if (
|
||||
!stash ||
|
||||
item.rights.rights !== stash.rights.rights ||
|
||||
item.rights.isOwner !== stash.rights.isOwner
|
||||
) {
|
||||
const variesRights = {
|
||||
icon: "CatalogQuestionIcon",
|
||||
rights: "Varies",
|
||||
isOwner: false,
|
||||
};
|
||||
item.rights = variesRights;
|
||||
}
|
||||
}
|
||||
length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setShareDataItemsFunction(allItems);
|
||||
};
|
||||
|
||||
removeDuplicateShareData = (shareDataItems) => {
|
||||
@ -379,104 +406,99 @@ class SharingPanelComponent extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
setDuplicateItemsRights = (shareDataItems, rights) => {
|
||||
const array = shareDataItems.slice(0);
|
||||
|
||||
let i = 0;
|
||||
while (array.length !== 0) {
|
||||
const item = array[i];
|
||||
array.splice(i, 1);
|
||||
const duplicateItem = array.find((x) => x.id === item.id);
|
||||
if (duplicateItem) {
|
||||
if (item.rights.rights !== duplicateItem.rights.rights) {
|
||||
const shareIndex = shareDataItems.findIndex(
|
||||
(x) => x.id === duplicateItem.id
|
||||
setShareDataItemsFunction = (shareDataItems) => {
|
||||
shareDataItems = shareDataItems.filter(
|
||||
(x) => x !== undefined && x.length !== 0
|
||||
);
|
||||
shareDataItems[shareIndex].rights = rights;
|
||||
}
|
||||
}
|
||||
}
|
||||
return shareDataItems;
|
||||
|
||||
const clearShareData = JSON.parse(JSON.stringify(shareDataItems));
|
||||
this.props.setShareDataItems(shareDataItems.slice(0));
|
||||
this.setState({ baseShareData: clearShareData });
|
||||
};
|
||||
|
||||
setOwnersRights = (listOwners, shareDataItems, rights) => {
|
||||
if (listOwners.length > 1) {
|
||||
while (listOwners.length !== 0) {
|
||||
const index = shareDataItems.findIndex((x) => x.id === listOwners[0]);
|
||||
shareDataItems[index].rights = rights;
|
||||
listOwners.splice(0, 1);
|
||||
}
|
||||
}
|
||||
return shareDataItems;
|
||||
};
|
||||
|
||||
getShareData() {
|
||||
getData = () => {
|
||||
const { selection, shareData } = this.props;
|
||||
|
||||
const foldersIds = [];
|
||||
const filesIds = [];
|
||||
|
||||
let shareDataItems = [];
|
||||
let listOwners = [];
|
||||
const shareDataItems = [];
|
||||
const folderId = [];
|
||||
const fileId = [];
|
||||
|
||||
for (let item of selection) {
|
||||
if (item.fileExst) {
|
||||
const itemShareData = shareData.find((x) => x.fileId === item.id);
|
||||
|
||||
if (itemShareData) {
|
||||
for (let item of itemShareData.shareDataItems) {
|
||||
shareDataItems.push(item);
|
||||
}
|
||||
shareDataItems.push(itemShareData.shareDataItems);
|
||||
} else {
|
||||
filesIds.push(item.id);
|
||||
fileId.push(item.id);
|
||||
}
|
||||
} else {
|
||||
const itemShareData = shareData.find((x) => x.folderId === item.id);
|
||||
if (itemShareData) {
|
||||
for (let item of itemShareData.shareDataItems) {
|
||||
shareDataItems.push(item);
|
||||
}
|
||||
shareDataItems.push(itemShareData.shareDataItems);
|
||||
} else {
|
||||
foldersIds.push(item.id);
|
||||
folderId.push(item.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getShareUsersAndGroups(foldersIds, filesIds)
|
||||
.then((res) => {
|
||||
//console.log("Response", res);
|
||||
|
||||
const shareDataResult = this.getShareDataItems(
|
||||
res,
|
||||
shareDataItems,
|
||||
foldersIds,
|
||||
filesIds
|
||||
);
|
||||
shareDataItems = shareDataResult[0];
|
||||
listOwners = shareDataResult[1];
|
||||
})
|
||||
.then(() => {
|
||||
const rights = {
|
||||
icon: "CatalogQuestionIcon",
|
||||
rights: "Varies",
|
||||
isOwner: false,
|
||||
return [shareDataItems, folderId, fileId];
|
||||
};
|
||||
|
||||
shareDataItems = shareDataItems.filter(
|
||||
(x) => x !== undefined && x.length !== 0
|
||||
);
|
||||
shareDataItems = this.setDuplicateItemsRights(shareDataItems, rights);
|
||||
shareDataItems = this.removeDuplicateShareData(shareDataItems);
|
||||
shareDataItems = this.setOwnersRights(
|
||||
listOwners,
|
||||
shareDataItems,
|
||||
rights
|
||||
);
|
||||
getShareData() {
|
||||
const returnValue = this.getData();
|
||||
let shareDataItems = returnValue[0];
|
||||
const folderId = returnValue[1];
|
||||
const fileId = returnValue[2];
|
||||
|
||||
const clearShareData = JSON.parse(JSON.stringify(shareDataItems));
|
||||
this.props.setShareDataItems(shareDataItems.slice(0));
|
||||
this.setState({ baseShareData: clearShareData });
|
||||
if (this.props.isRecycleBinFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (folderId.length !== 0) {
|
||||
files
|
||||
.getShareFolders(folderId)
|
||||
.then((res) => {
|
||||
shareDataItems = this.getShareDataItems(
|
||||
res,
|
||||
shareDataItems,
|
||||
folderId,
|
||||
null
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
const newShareDataItem = [
|
||||
{ folderId: folderId[0], shareDataItems: [] },
|
||||
];
|
||||
this.props.setShareData([
|
||||
...newShareDataItem,
|
||||
...this.props.shareData,
|
||||
]);
|
||||
console.log("getShareFolders", err);
|
||||
return;
|
||||
});
|
||||
} else if (fileId.length !== 0) {
|
||||
files
|
||||
.getShareFiles(fileId)
|
||||
.then((res) => {
|
||||
shareDataItems = this.getShareDataItems(
|
||||
res,
|
||||
shareDataItems,
|
||||
null,
|
||||
fileId
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
const newShareDataItem = [{ fileId: fileId[0], shareDataItems: [] }];
|
||||
this.props.setShareData([
|
||||
...newShareDataItem,
|
||||
...this.props.shareData,
|
||||
]);
|
||||
console.log("getShareFiles", err);
|
||||
return;
|
||||
});
|
||||
} else {
|
||||
shareDataItems = this.getShareDataItems([], shareDataItems, null, fileId);
|
||||
}
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
@ -485,8 +507,11 @@ class SharingPanelComponent extends React.Component {
|
||||
this.props.onClose();
|
||||
};
|
||||
|
||||
onShowEmbeddingPanel = link =>
|
||||
this.setState({ showEmbeddingPanel: !this.state.showEmbeddingPanel, shareLink: link });
|
||||
onShowEmbeddingPanel = (link) =>
|
||||
this.setState({
|
||||
showEmbeddingPanel: !this.state.showEmbeddingPanel,
|
||||
shareLink: link,
|
||||
});
|
||||
|
||||
onShowGroupsPanel = () =>
|
||||
this.setState({ showAddGroupsPanel: !this.state.showAddGroupsPanel });
|
||||
@ -515,7 +540,7 @@ class SharingPanelComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
//console.log("Sharing panel render");
|
||||
const { visible, t, accessOptions, isMe, selection } = this.props;
|
||||
const { visible, t, accessOptions, isMyId, selection } = this.props;
|
||||
const {
|
||||
showActionPanel,
|
||||
isNotifyUsers,
|
||||
@ -525,7 +550,7 @@ class SharingPanelComponent extends React.Component {
|
||||
showAddGroupsPanel,
|
||||
showEmbeddingPanel,
|
||||
accessRight,
|
||||
shareLink
|
||||
shareLink,
|
||||
} = this.state;
|
||||
|
||||
const zIndex = 310;
|
||||
@ -636,11 +661,11 @@ class SharingPanelComponent extends React.Component {
|
||||
</DropDown>
|
||||
</div>
|
||||
|
||||
<IconButton
|
||||
{/*<IconButton
|
||||
size="16"
|
||||
iconName="KeyIcon"
|
||||
onClick={this.onKeyClick}
|
||||
/>
|
||||
/>*/}
|
||||
</div>
|
||||
</StyledSharingHeaderContent>
|
||||
<StyledSharingBody>
|
||||
@ -651,7 +676,7 @@ class SharingPanelComponent extends React.Component {
|
||||
selection={selection}
|
||||
item={item}
|
||||
index={index}
|
||||
isMe={isMe}
|
||||
isMyId={isMyId}
|
||||
accessOptions={accessOptions}
|
||||
onFullAccessClick={this.onFullAccessItemClick}
|
||||
onReadOnlyClick={this.onReadOnlyItemClick}
|
||||
@ -723,7 +748,7 @@ class SharingPanelComponent extends React.Component {
|
||||
|
||||
SharingPanelComponent.propTypes = {
|
||||
onClose: PropTypes.func,
|
||||
visible: PropTypes.bool
|
||||
visible: PropTypes.bool,
|
||||
};
|
||||
|
||||
const SharingPanelContainerTranslated = withTranslation()(
|
||||
@ -735,9 +760,26 @@ const SharingPanel = (props) => (
|
||||
);
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { shareDataItems, shareData, selection } = state.files;
|
||||
const {
|
||||
shareDataItems,
|
||||
shareData,
|
||||
selection,
|
||||
selectedFolder,
|
||||
treeFolders,
|
||||
} = state.files;
|
||||
const indexOfTrash = 3;
|
||||
|
||||
return { shareDataItems, shareData, selection, isMe: state.auth.user.id };
|
||||
return {
|
||||
shareDataItems,
|
||||
shareData,
|
||||
selection,
|
||||
isMyId: state.auth.user.id,
|
||||
isRecycleBinFolder: checkFolderType(
|
||||
selectedFolder.id,
|
||||
indexOfTrash,
|
||||
treeFolders
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, { setShareDataItems, setShareData })(
|
||||
|
@ -17,7 +17,7 @@ const SharingRow = (props) => {
|
||||
selection,
|
||||
item,
|
||||
index,
|
||||
isMe,
|
||||
isMyId,
|
||||
accessOptions,
|
||||
onFullAccessClick,
|
||||
onReadOnlyClick,
|
||||
@ -28,17 +28,69 @@ const SharingRow = (props) => {
|
||||
onRemoveUserClick,
|
||||
onShowEmbeddingPanel,
|
||||
} = props;
|
||||
|
||||
const linkVisible = selection.length === 1 && item.shareLink;
|
||||
|
||||
const onCopyInternalLink = (link) => {
|
||||
const internalLink = link.split("&");
|
||||
const onCopyInternalLink = () => {
|
||||
const internalLink = item.shareLink.split("&");
|
||||
copy(`${internalLink[0]}&${internalLink[1]}`);
|
||||
toastr.success(t("LinkCopySuccess"));
|
||||
};
|
||||
|
||||
const embeddedComponentRender = (accessOptions, item) => (
|
||||
const advancedOptionsRender = () => (
|
||||
<>
|
||||
{accessOptions.includes("FullAccess") && (
|
||||
<DropDownItem
|
||||
label="Full access"
|
||||
icon="AccessEditIcon"
|
||||
onClick={() => onFullAccessClick(item)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{accessOptions.includes("ReadOnly") && (
|
||||
<DropDownItem
|
||||
label="Read only"
|
||||
icon="EyeIcon"
|
||||
onClick={() => onReadOnlyClick(item)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{accessOptions.includes("Review") && (
|
||||
<DropDownItem
|
||||
label="Review"
|
||||
icon="AccessReviewIcon"
|
||||
onClick={() => onReviewClick(item)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{accessOptions.includes("Comment") && (
|
||||
<DropDownItem
|
||||
label="Comment"
|
||||
icon="AccessCommentIcon"
|
||||
onClick={() => onCommentClick(item)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{accessOptions.includes("FormFilling") && (
|
||||
<DropDownItem
|
||||
label="Form filling"
|
||||
icon="AccessFormIcon"
|
||||
onClick={() => onFormFillingClick(item)}
|
||||
/>
|
||||
)}
|
||||
{accessOptions.includes("DenyAccess") && (
|
||||
<DropDownItem
|
||||
label="Deny access"
|
||||
icon="AccessNoneIcon"
|
||||
onClick={() => onDenyAccessClick(item)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const embeddedComponentRender = () => (
|
||||
<ComboBox
|
||||
advancedOptions={advancedOptionsRender(accessOptions, item)}
|
||||
advancedOptions={advancedOptionsRender()}
|
||||
options={[]}
|
||||
selectedOption={{ key: 0 }}
|
||||
size="content"
|
||||
@ -54,57 +106,6 @@ const SharingRow = (props) => {
|
||||
</ComboBox>
|
||||
);
|
||||
|
||||
const advancedOptionsRender = (accessOptions, item) => (
|
||||
<>
|
||||
{accessOptions.includes("FullAccess") && (
|
||||
<DropDownItem
|
||||
label="Full access"
|
||||
icon="AccessEditIcon"
|
||||
onClick={onFullAccessClick.bind(this, item)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{accessOptions.includes("ReadOnly") && (
|
||||
<DropDownItem
|
||||
label="Read only"
|
||||
icon="EyeIcon"
|
||||
onClick={onReadOnlyClick.bind(this, item)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{accessOptions.includes("Review") && (
|
||||
<DropDownItem
|
||||
label="Review"
|
||||
icon="AccessReviewIcon"
|
||||
onClick={onReviewClick.bind(this, item)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{accessOptions.includes("Comment") && (
|
||||
<DropDownItem
|
||||
label="Comment"
|
||||
icon="AccessCommentIcon"
|
||||
onClick={onCommentClick.bind(this, item)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{accessOptions.includes("FormFilling") && (
|
||||
<DropDownItem
|
||||
label="Form filling"
|
||||
icon="AccessFormIcon"
|
||||
onClick={onFormFillingClick.bind(this, item)}
|
||||
/>
|
||||
)}
|
||||
{accessOptions.includes("DenyAccess") && (
|
||||
<DropDownItem
|
||||
label="Deny access"
|
||||
icon="AccessNoneIcon"
|
||||
onClick={onDenyAccessClick.bind(this, item)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const options = [
|
||||
{
|
||||
key: 1,
|
||||
@ -153,8 +154,8 @@ const SharingRow = (props) => {
|
||||
const internalLinkData = [
|
||||
{
|
||||
key: "linkItem",
|
||||
label: "Copy internal link",
|
||||
onClick: onCopyInternalLink.bind(this, item.shareLink),
|
||||
label: t("CopyInternalLink"),
|
||||
onClick: onCopyInternalLink,
|
||||
},
|
||||
];
|
||||
|
||||
@ -199,13 +200,16 @@ const SharingRow = (props) => {
|
||||
},
|
||||
];
|
||||
|
||||
//console.log("SharingRow render");
|
||||
return (
|
||||
<>
|
||||
{linkVisible && (
|
||||
const linksFunction = (linkText, data) => (
|
||||
<Row
|
||||
key={`external-link-key_${index}`}
|
||||
element={embeddedComponentRender(accessOptions, item)}
|
||||
key={`${linkText}-key_${index}`}
|
||||
//element={embeddedComponentRender(accessOptions, item)}
|
||||
element={
|
||||
<Icons.AccessEditIcon
|
||||
size="medium"
|
||||
className="sharing_panel-owner-icon"
|
||||
/>
|
||||
}
|
||||
contextButtonSpacerWidth="0px"
|
||||
>
|
||||
<>
|
||||
@ -213,10 +217,11 @@ const SharingRow = (props) => {
|
||||
className="sharing_panel-link"
|
||||
color="black"
|
||||
dropdownType="alwaysDashed"
|
||||
data={externalLinkData}
|
||||
data={data}
|
||||
>
|
||||
{t("ExternalLink")}
|
||||
{t(linkText)}
|
||||
</LinkWithDropdown>
|
||||
{/*
|
||||
<ComboBox
|
||||
className="sharing_panel-link-combo-box"
|
||||
options={options}
|
||||
@ -225,18 +230,26 @@ const SharingRow = (props) => {
|
||||
dropDownMaxHeight={200}
|
||||
noBorder={false}
|
||||
scaled={false}
|
||||
scaledOptions={true}
|
||||
scaledOptions
|
||||
size="content"
|
||||
onSelect={(option) => console.log("selected", option)}
|
||||
/>
|
||||
*/
|
||||
}
|
||||
</>
|
||||
</Row>
|
||||
)}
|
||||
{(linkVisible || !item.shareLink) && (
|
||||
);
|
||||
|
||||
//console.log("SharingRow render");
|
||||
return (
|
||||
<>
|
||||
{linkVisible && linksFunction("ExternalLink", externalLinkData)}
|
||||
{linkVisible && linksFunction("InternalLink", internalLinkData)}
|
||||
{!item.shareLink && (
|
||||
<Row
|
||||
key={`internal-link-key_${index}`}
|
||||
element={
|
||||
item.rights.isOwner || item.id === isMe ? (
|
||||
item.rights.isOwner || item.id === isMyId ? (
|
||||
<Icons.AccessEditIcon
|
||||
size="medium"
|
||||
className="sharing_panel-owner-icon"
|
||||
@ -248,17 +261,7 @@ const SharingRow = (props) => {
|
||||
contextButtonSpacerWidth="0px"
|
||||
>
|
||||
<>
|
||||
{item.shareLink ? (
|
||||
<LinkWithDropdown
|
||||
className="sharing_panel-link"
|
||||
color="black"
|
||||
dropdownType="alwaysDashed"
|
||||
data={internalLinkData}
|
||||
>
|
||||
{t("InternalLink")}
|
||||
</LinkWithDropdown>
|
||||
) : (
|
||||
!item.shareLink && (
|
||||
{!item.shareLink && (
|
||||
<Text truncate className="sharing_panel-text">
|
||||
{item.label
|
||||
? item.label
|
||||
@ -266,9 +269,7 @@ const SharingRow = (props) => {
|
||||
? item.name
|
||||
: item.displayName}
|
||||
</Text>
|
||||
)
|
||||
)}
|
||||
|
||||
{item.rights.isOwner ? (
|
||||
<Text
|
||||
className="sharing_panel-remove-icon"
|
||||
@ -276,31 +277,18 @@ const SharingRow = (props) => {
|
||||
>
|
||||
{t("Owner")}
|
||||
</Text>
|
||||
) : item.id === isMe ? (
|
||||
) : item.id === isMyId ? (
|
||||
<Text
|
||||
className="sharing_panel-remove-icon"
|
||||
//color="#A3A9AE"
|
||||
>
|
||||
{t("AccessRightsFullAccess")}
|
||||
</Text>
|
||||
) : item.shareLink ? (
|
||||
<ComboBox
|
||||
className="sharing_panel-link-combo-box"
|
||||
options={options}
|
||||
isDisabled={false}
|
||||
selectedOption={options[0]}
|
||||
dropDownMaxHeight={200}
|
||||
noBorder={false}
|
||||
scaled={false}
|
||||
scaledOptions={true}
|
||||
size="content"
|
||||
onSelect={(option) => console.log("selected", option)}
|
||||
/>
|
||||
) : (
|
||||
!item.shareLink && (
|
||||
<IconButton
|
||||
iconName="RemoveIcon"
|
||||
onClick={onRemoveUserClick.bind(this, item)}
|
||||
onClick={() => onRemoveUserClick(item)}
|
||||
className="sharing_panel-remove-icon"
|
||||
/>
|
||||
)
|
||||
@ -312,4 +300,4 @@ const SharingRow = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SharingRow);
|
||||
export default SharingRow;
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
"Notify users": "Notify users",
|
||||
"CopyExternalLink": "Copy external link",
|
||||
"CopyInternalLink": "Copy internal link",
|
||||
"ShareVia": "Share via",
|
||||
"Embedding": "Embedding",
|
||||
"ExternalLink": "External link",
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
"Notify users": "Уведомить пользователей",
|
||||
"CopyExternalLink": "Скопировать внешнюю ссылку",
|
||||
"CopyInternalLink": "Скопировать внутреннюю ссылку",
|
||||
"ShareVia": "Отправить по",
|
||||
"Embedding": "Встраивание",
|
||||
"ExternalLink": "Внешняя ссылка",
|
||||
|
@ -36,7 +36,7 @@ if (token) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
return Promise.resolve(FilesFilter.getDefault());
|
||||
return Promise.resolve();
|
||||
}
|
||||
})
|
||||
.then(filter => {
|
||||
@ -64,6 +64,8 @@ if (token) {
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
if (!data) return Promise.resolve();
|
||||
|
||||
if (data instanceof FilesFilter) return Promise.resolve(data);
|
||||
const { filter, itemId, type } = data;
|
||||
const newFilter = filter ? filter.clone() : FilesFilter.getDefault();
|
||||
@ -78,6 +80,8 @@ if (token) {
|
||||
})
|
||||
.catch(err => Promise.resolve(FilesFilter.getDefault()))
|
||||
.then(data => {
|
||||
if (!data) return Promise.resolve();
|
||||
|
||||
if (data instanceof FilesFilter) return Promise.resolve(data);
|
||||
const result = data[0];
|
||||
const filter = data[1];
|
||||
@ -91,6 +95,8 @@ if (token) {
|
||||
return Promise.resolve(filter);
|
||||
})
|
||||
.then(filter => {
|
||||
if (!filter) return Promise.resolve();
|
||||
|
||||
const folderId = filter.folder;
|
||||
return fetchFiles(folderId, filter, store.dispatch);
|
||||
})
|
||||
|
@ -322,20 +322,6 @@ export function deleteFolder(folderId, deleteAfter, immediately) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getShareUsersAndGroups(foldersIds, filesIds) {
|
||||
const getFoldersRequests = foldersIds.map(id =>
|
||||
files.getShareFolders(id).catch(() => {
|
||||
return [];
|
||||
})
|
||||
);
|
||||
const getFilesRequests = filesIds.map(id =>
|
||||
files.getShareFiles(id).catch(() => [])
|
||||
);
|
||||
const requests = [...getFoldersRequests, ...getFilesRequests];
|
||||
|
||||
return axios.all(requests).then(res => res);
|
||||
}
|
||||
|
||||
export function setSharedFolders(folderIds, shareTo, access, notify, sharingMessage) {
|
||||
const requests = folderIds.map((id) =>
|
||||
files.setShareFolder(id, shareTo, access, notify, sharingMessage)
|
||||
|
@ -158,6 +158,75 @@ export const getTreeFolders = (pathParts, filterData) => {
|
||||
return treeFolders;
|
||||
};
|
||||
|
||||
const renameTreeFolder = (folders, newItems, currentFolder) => {
|
||||
const newItem = folders.find(x => x.id === currentFolder.id);
|
||||
const oldItemIndex = newItems.folders.findIndex(x => x.id === currentFolder.id);
|
||||
newItem.folders = newItems.folders[oldItemIndex].folders;
|
||||
newItems.folders[oldItemIndex] = newItem;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const removeTreeFolder = (folders, newItems, foldersCount) => {
|
||||
for (let folder of newItems.folders) {
|
||||
let currentFolder;
|
||||
if(folders) {
|
||||
currentFolder = folders.find((x) => x.id === folder.id);
|
||||
}
|
||||
|
||||
if (!currentFolder) {
|
||||
const arrayFolders = newItems.folders.filter(x => x.id !== folder.id);
|
||||
newItems.folders = arrayFolders;
|
||||
newItems.foldersCount = foldersCount;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const addTreeFolder = (folders, newItems, foldersCount) => {
|
||||
let array;
|
||||
const newItemFolders = newItems.folders ? newItems.folders : [];
|
||||
for (let folder of folders) {
|
||||
let currentFolder;
|
||||
if(newItemFolders) {
|
||||
currentFolder = newItemFolders.find((x) => x.id === folder.id);
|
||||
}
|
||||
|
||||
if (folders.length < 1 || !currentFolder) {
|
||||
array = [...newItemFolders, ...[folder]].sort((prev, next) =>
|
||||
prev.title.toLowerCase() < next.title.toLowerCase() ? -1 : 1
|
||||
);
|
||||
newItems.folders = array;
|
||||
newItems.foldersCount = foldersCount;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const loopTreeFolders = (path, item, folders, foldersCount, currentFolder) => {
|
||||
const newPath = path;
|
||||
while (path.length !== 0) {
|
||||
const newItems = item.find((x) => x.id === path[0]);
|
||||
if(!newItems) { return; }
|
||||
newPath.shift();
|
||||
if (path.length === 0) {
|
||||
let foldersLength = newItems.folders ? newItems.folders.length : 0;
|
||||
//new Date(prev.updated) > new Date(next.updated) ? -1 : 1;
|
||||
if (folders.length > foldersLength) {
|
||||
addTreeFolder(folders, newItems, foldersCount);
|
||||
} else if (folders.length < foldersLength) {
|
||||
removeTreeFolder(folders, newItems, foldersCount);
|
||||
} else if (folders.length > 0 && newItems.length > 0) {
|
||||
renameTreeFolder(folders, newItems, currentFolder);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
loopTreeFolders(newPath, newItems.folders, folders, foldersCount, currentFolder);
|
||||
}
|
||||
}
|
||||
|
||||
export const isCanCreate = (selectedFolder, user) => {
|
||||
if (!selectedFolder || !selectedFolder.id) return;
|
||||
|
||||
@ -319,3 +388,20 @@ export const getFileIcon = (extension, size = 32) => {
|
||||
return `${folderPath}/file.svg`;
|
||||
}
|
||||
}
|
||||
|
||||
export const checkFolderType = (id, index, treeFolders) => {
|
||||
return treeFolders[index].id === id;
|
||||
}
|
||||
|
||||
export const getFolderType = (id, treeFolders) => {
|
||||
|
||||
const indexOfMy = 0;
|
||||
const indexOfShare = 1;
|
||||
const indexOfCommon = 2;
|
||||
const indexOfTrash = 3;
|
||||
|
||||
if(checkFolderType(id, indexOfMy, treeFolders)) { return "My"; }
|
||||
else if(checkFolderType(id, indexOfShare, treeFolders)) { return "Share"; }
|
||||
else if(checkFolderType(id, indexOfCommon, treeFolders)) { return "Common"; }
|
||||
else if(checkFolderType(id, indexOfTrash, treeFolders)) { return "Trash"; }
|
||||
}
|
@ -546,15 +546,15 @@ namespace ASC.Api.Documents
|
||||
/// ]]>
|
||||
/// </returns>
|
||||
[Create("{folderId}/upload/create_session", DisableFormat = true)]
|
||||
public object CreateUploadSession(string folderId, string fileName, long fileSize, string relativePath, bool encrypted)
|
||||
public object CreateUploadSession(string folderId, SessionModel sessionModel)
|
||||
{
|
||||
return FilesControllerHelperString.CreateUploadSession(folderId, fileName, fileSize, relativePath, encrypted);
|
||||
return FilesControllerHelperString.CreateUploadSession(folderId, sessionModel.FileName, sessionModel.FileSize, sessionModel.RelativePath, sessionModel.Encrypted);
|
||||
}
|
||||
|
||||
[Create("{folderId:int}/upload/create_session")]
|
||||
public object CreateUploadSession(int folderId, string fileName, long fileSize, string relativePath, bool encrypted)
|
||||
public object CreateUploadSession(int folderId, SessionModel sessionModel)
|
||||
{
|
||||
return FilesControllerHelperInt.CreateUploadSession(folderId, fileName, fileSize, relativePath, encrypted);
|
||||
return FilesControllerHelperInt.CreateUploadSession(folderId, sessionModel.FileName, sessionModel.FileSize, sessionModel.RelativePath, sessionModel.Encrypted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -630,7 +630,11 @@ namespace ASC.Files.Core.Data
|
||||
if (fileId == default) return;
|
||||
using var tx = FilesDbContext.Database.BeginTransaction();
|
||||
|
||||
var fromFolders = Query(FilesDbContext.Files).Where(r => r.Id == fileId).GroupBy(r => r.Id).SelectMany(r => r.Select(a => a.FolderId)).Distinct().ToList();
|
||||
var fromFolders = Query(FilesDbContext.Files)
|
||||
.Where(r => r.Id == fileId)
|
||||
.Select(a => a.FolderId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var toDeleteFiles = Query(FilesDbContext.Files).Where(r => r.Id == fileId);
|
||||
FilesDbContext.RemoveRange(toDeleteFiles);
|
||||
|
@ -46,6 +46,7 @@ using ASC.Web.Studio.Core;
|
||||
using ASC.Web.Studio.UserControls.Statistics;
|
||||
using ASC.Web.Studio.Utility;
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ASC.Files.Core.Data
|
||||
@ -285,6 +286,11 @@ namespace ASC.Files.Core.Data
|
||||
}
|
||||
|
||||
public int SaveFolder(Folder<int> folder)
|
||||
{
|
||||
return SaveFolder(folder, null);
|
||||
}
|
||||
|
||||
public int SaveFolder(Folder<int> folder, IDbContextTransaction transaction)
|
||||
{
|
||||
if (folder == null) throw new ArgumentNullException("folder");
|
||||
|
||||
@ -298,8 +304,8 @@ namespace ASC.Files.Core.Data
|
||||
|
||||
var isnew = false;
|
||||
|
||||
using (var tx = FilesDbContext.Database.BeginTransaction())
|
||||
{
|
||||
var tx = transaction ?? FilesDbContext.Database.BeginTransaction();
|
||||
|
||||
if (folder.ID != default && IsExist(folder.ID))
|
||||
{
|
||||
var toUpdate = Query(FilesDbContext.Folders)
|
||||
@ -363,7 +369,10 @@ namespace ASC.Files.Core.Data
|
||||
FilesDbContext.SaveChanges();
|
||||
}
|
||||
|
||||
if (transaction == null)
|
||||
{
|
||||
tx.Commit();
|
||||
tx.Dispose();
|
||||
}
|
||||
|
||||
if (isnew)
|
||||
@ -835,7 +844,7 @@ namespace ASC.Files.Core.Data
|
||||
}
|
||||
using var tx = FilesDbContext.Database.BeginTransaction();//NOTE: Maybe we shouldn't start transaction here at all
|
||||
|
||||
newFolderId = SaveFolder(folder); //Save using our db manager
|
||||
newFolderId = SaveFolder(folder, tx); //Save using our db manager
|
||||
|
||||
var newBunch = new DbFilesBunchObjects
|
||||
{
|
||||
@ -905,7 +914,7 @@ namespace ASC.Files.Core.Data
|
||||
break;
|
||||
}
|
||||
using var tx = FilesDbContext.Database.BeginTransaction(); //NOTE: Maybe we shouldn't start transaction here at all
|
||||
newFolderId = SaveFolder(folder); //Save using our db manager
|
||||
newFolderId = SaveFolder(folder, tx); //Save using our db manager
|
||||
var toInsert = new DbFilesBunchObjects
|
||||
{
|
||||
LeftNode = newFolderId.ToString(),
|
||||
|
@ -416,7 +416,7 @@ namespace ASC.Files.Core.Data
|
||||
|
||||
public IEnumerable<Tag> GetNewTags(Guid subject, Folder<T> parentFolder, bool deepSearch)
|
||||
{
|
||||
if (parentFolder == null || parentFolder.ID.Equals(default))
|
||||
if (parentFolder == null || parentFolder.ID.Equals(default(T)))
|
||||
throw new ArgumentException("folderId");
|
||||
|
||||
var result = new List<Tag>();
|
||||
|
@ -215,9 +215,15 @@ namespace ASC.Files.Core
|
||||
}
|
||||
|
||||
public object NativeAccessor { get; set; }
|
||||
public FilesLinkUtility FilesLinkUtility { get; }
|
||||
public FileUtility FileUtility { get; }
|
||||
public FileConverter FileConverter { get; }
|
||||
|
||||
[NonSerialized]
|
||||
private readonly FilesLinkUtility FilesLinkUtility;
|
||||
|
||||
[NonSerialized]
|
||||
private readonly FileUtility FileUtility;
|
||||
|
||||
[NonSerialized]
|
||||
private readonly FileConverter FileConverter;
|
||||
|
||||
private T _folderIdDisplay;
|
||||
|
||||
@ -235,10 +241,8 @@ namespace ASC.Files.Core
|
||||
|
||||
public static string Serialize(File<T> file)
|
||||
{
|
||||
using (var ms = new FileEntrySerializer().ToXml(file))
|
||||
{
|
||||
using var ms = new FileEntrySerializer().ToXml(file);
|
||||
return Encoding.UTF8.GetString(ms.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -32,6 +32,8 @@ using ASC.Web.Files.Classes;
|
||||
|
||||
namespace ASC.Files.Core
|
||||
{
|
||||
[DataContract(Name = "entry", Namespace = "")]
|
||||
[Serializable]
|
||||
public abstract class FileEntry : ICloneable
|
||||
{
|
||||
public FileEntry(Global global)
|
||||
@ -107,7 +109,8 @@ namespace ASC.Files.Core
|
||||
|
||||
public FileEntryType FileEntryType;
|
||||
|
||||
public Global Global { get; }
|
||||
[NonSerialized]
|
||||
protected Global Global;
|
||||
|
||||
private string _modifiedByString;
|
||||
private string _createByString;
|
||||
|
@ -474,7 +474,7 @@ namespace ASC.Web.Files.Services.WCFService
|
||||
ErrorIf(file == null, FilesCommonResource.ErrorMassage_FileNotFound);
|
||||
ErrorIf(!FileSecurity.CanRead(file), FilesCommonResource.ErrorMassage_SecurityException_ReadFile);
|
||||
|
||||
var parent = folderDao.GetFolder(parentId == null || parentId.Equals(default) ? file.FolderID : parentId);
|
||||
var parent = folderDao.GetFolder(parentId == null || parentId.Equals(default(T)) ? file.FolderID : parentId);
|
||||
ErrorIf(parent == null, FilesCommonResource.ErrorMassage_FolderNotFound);
|
||||
ErrorIf(parent.RootFolderType == FolderType.TRASH, FilesCommonResource.ErrorMassage_ViewTrashItem);
|
||||
|
||||
|
@ -74,7 +74,6 @@ namespace ASC.Web.Files.Helpers
|
||||
public TokenHelper TokenHelper { get; }
|
||||
public AuthContext AuthContext { get; }
|
||||
public ConsumerFactory ConsumerFactory { get; }
|
||||
public DocuSignLoginProvider DocuSignLoginProvider { get; }
|
||||
|
||||
public DocuSignToken(
|
||||
TokenHelper tokenHelper,
|
||||
@ -154,7 +153,6 @@ namespace ASC.Web.Files.Helpers
|
||||
public static int MaxEmailLength = 10000;
|
||||
|
||||
public DocuSignToken DocuSignToken { get; }
|
||||
public DocuSignLoginProvider DocuSignLoginProvider { get; }
|
||||
public FileSecurity FileSecurity { get; }
|
||||
public IDaoFactory DaoFactory { get; }
|
||||
public BaseCommonLinkUtility BaseCommonLinkUtility { get; }
|
||||
@ -170,7 +168,6 @@ namespace ASC.Web.Files.Helpers
|
||||
|
||||
public DocuSignHelper(
|
||||
DocuSignToken docuSignToken,
|
||||
DocuSignLoginProvider docuSignLoginProvider,
|
||||
FileSecurity fileSecurity,
|
||||
IDaoFactory daoFactory,
|
||||
IOptionsMonitor<ILog> options,
|
||||
@ -186,7 +183,6 @@ namespace ASC.Web.Files.Helpers
|
||||
ConsumerFactory consumerFactory)
|
||||
{
|
||||
DocuSignToken = docuSignToken;
|
||||
DocuSignLoginProvider = docuSignLoginProvider;
|
||||
FileSecurity = fileSecurity;
|
||||
DaoFactory = daoFactory;
|
||||
BaseCommonLinkUtility = baseCommonLinkUtility;
|
||||
@ -319,7 +315,7 @@ namespace ASC.Web.Files.Helpers
|
||||
// new RecipientEvent {RecipientEventStatusCode = "AuthenticationFailed"},
|
||||
// new RecipientEvent {RecipientEventStatusCode = "AutoResponded"},
|
||||
// },
|
||||
Url = BaseCommonLinkUtility.GetFullAbsolutePath(DocuSignHandler.Path(FilesLinkUtility) + "?" + FilesLinkUtility.Action + "=webhook"),
|
||||
Url = BaseCommonLinkUtility.GetFullAbsolutePath(DocuSignHandlerService.Path(FilesLinkUtility) + "?" + FilesLinkUtility.Action + "=webhook"),
|
||||
};
|
||||
|
||||
Log.Debug("DocuSign hook url: " + eventNotification.Url);
|
||||
@ -371,7 +367,7 @@ namespace ASC.Web.Files.Helpers
|
||||
var envelopeId = envelopeSummary.EnvelopeId;
|
||||
var url = envelopesApi.CreateSenderView(accountId, envelopeId, new ReturnUrlRequest
|
||||
{
|
||||
ReturnUrl = BaseCommonLinkUtility.GetFullAbsolutePath(DocuSignHandler.Path(FilesLinkUtility) + "?" + FilesLinkUtility.Action + "=redirect")
|
||||
ReturnUrl = BaseCommonLinkUtility.GetFullAbsolutePath(DocuSignHandlerService.Path(FilesLinkUtility) + "?" + FilesLinkUtility.Action + "=redirect")
|
||||
});
|
||||
Log.Debug("DocuSign senderView: " + url.Url);
|
||||
|
||||
|
@ -251,13 +251,13 @@ namespace ASC.Files.Helpers
|
||||
|
||||
if (FilesLinkUtility.IsLocalFileUploader)
|
||||
{
|
||||
var session = FileUploader.InitiateUpload(file.FolderID.ToString(), (file.ID ?? default).ToString(), file.Title, file.ContentLength, encrypted);
|
||||
var session = FileUploader.InitiateUpload(file.FolderID, (file.ID ?? default), file.Title, file.ContentLength, encrypted);
|
||||
|
||||
var response = ChunkedUploadSessionHelper.ToResponseObject(session, true);
|
||||
var responseObject = ChunkedUploadSessionHelper.ToResponseObject(session, true);
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
data = response
|
||||
data = responseObject
|
||||
};
|
||||
}
|
||||
|
||||
@ -279,11 +279,10 @@ namespace ASC.Files.Helpers
|
||||
ServicePointManager.ServerCertificateValidationCallback += (s, ce, ca, p) => true;
|
||||
}
|
||||
|
||||
using (var response = request.GetResponse())
|
||||
using (var responseStream = response.GetResponseStream())
|
||||
{
|
||||
return JObject.Parse(new StreamReader(responseStream).ReadToEnd()); //result is json string
|
||||
}
|
||||
using var response = request.GetResponse();
|
||||
using var responseStream = response.GetResponseStream();
|
||||
using var streamReader = new StreamReader(responseStream);
|
||||
return JObject.Parse(streamReader.ReadToEnd()); //result is json string
|
||||
}
|
||||
|
||||
public FileWrapper<T> CreateTextFile(T folderId, string title, string content)
|
||||
|
@ -64,7 +64,7 @@ namespace ASC.Web.Files.Helpers
|
||||
|
||||
public IEnumerable<string> ThirdPartyProviders
|
||||
{
|
||||
get { return (Configuration["files:thirdparty:enable"] ?? "").Split(new char[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries); }
|
||||
get { return (Configuration.GetSection("files:thirdparty:enable").Get<string[]>() ?? new string[] { }).ToList(); }
|
||||
}
|
||||
|
||||
public bool SupportInclusion
|
||||
|
@ -45,16 +45,37 @@ using ASC.Web.Files.Helpers;
|
||||
using ASC.Web.Files.Utils;
|
||||
using ASC.Web.Studio.Core;
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ASC.Web.Files.HttpHandlers
|
||||
{
|
||||
public class ChunkedUploaderHandler //: AbstractHttpAsyncHandler
|
||||
public class ChunkedUploaderHandler
|
||||
{
|
||||
public RequestDelegate Next { get; }
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
public ChunkedUploaderHandler(RequestDelegate next, IServiceProvider serviceProvider)
|
||||
{
|
||||
Next = next;
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
using var scope = ServiceProvider.CreateScope();
|
||||
var chunkedUploaderHandlerService = scope.ServiceProvider.GetService<ChunkedUploaderHandlerService>();
|
||||
await chunkedUploaderHandlerService.Invoke(context);
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
}
|
||||
|
||||
public class ChunkedUploaderHandlerService
|
||||
{
|
||||
public TenantManager TenantManager { get; }
|
||||
public FileUploader FileUploader { get; }
|
||||
public FilesMessageService FilesMessageService { get; }
|
||||
@ -67,8 +88,7 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
public ChunkedUploadSessionHelper ChunkedUploadSessionHelper { get; }
|
||||
public ILog Logger { get; }
|
||||
|
||||
public ChunkedUploaderHandler(
|
||||
RequestDelegate next,
|
||||
public ChunkedUploaderHandlerService(
|
||||
IOptionsMonitor<ILog> optionsMonitor,
|
||||
TenantManager tenantManager,
|
||||
FileUploader fileUploader,
|
||||
@ -81,7 +101,6 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
ChunkedUploadSessionHolder chunkedUploadSessionHolder,
|
||||
ChunkedUploadSessionHelper chunkedUploadSessionHelper)
|
||||
{
|
||||
Next = next;
|
||||
TenantManager = tenantManager;
|
||||
FileUploader = fileUploader;
|
||||
FilesMessageService = filesMessageService;
|
||||
@ -96,69 +115,80 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
var uploadSession = ChunkedUploadSessionHolder.GetSession(context.Request.Query["uid"]);
|
||||
|
||||
if (uploadSession as ChunkedUploadSession<int> != null)
|
||||
{
|
||||
await Invoke<int>(context);
|
||||
return;
|
||||
}
|
||||
|
||||
await Invoke<string>(context);
|
||||
}
|
||||
|
||||
public async Task Invoke<T>(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new ChunkedRequestHelper(context.Request);
|
||||
var request = new ChunkedRequestHelper<T>(context.Request);
|
||||
|
||||
if (!TryAuthorize(request))
|
||||
{
|
||||
WriteError(context, "Can't authorize given initiate session request or session with specified upload id already expired");
|
||||
await WriteError(context, "Can't authorize given initiate session request or session with specified upload id already expired");
|
||||
return;
|
||||
}
|
||||
|
||||
if (TenantManager.GetCurrentTenant().Status != TenantStatus.Active)
|
||||
{
|
||||
WriteError(context, "Can't perform upload for deleted or transfering portals");
|
||||
await WriteError(context, "Can't perform upload for deleted or transfering portals");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (request.Type(InstanceCrypto))
|
||||
{
|
||||
case ChunkedRequestType.Abort:
|
||||
FileUploader.AbortUpload<string>(request.UploadId);
|
||||
WriteSuccess(context, null);
|
||||
FileUploader.AbortUpload<T>(request.UploadId);
|
||||
await WriteSuccess(context, null);
|
||||
return;
|
||||
|
||||
case ChunkedRequestType.Initiate:
|
||||
var createdSession = FileUploader.InitiateUpload(request.FolderId, request.FileId, request.FileName, request.FileSize, request.Encrypted);
|
||||
WriteSuccess(context, ChunkedUploadSessionHelper.ToResponseObject(createdSession, true));
|
||||
await WriteSuccess(context, ChunkedUploadSessionHelper.ToResponseObject(createdSession, true));
|
||||
return;
|
||||
|
||||
case ChunkedRequestType.Upload:
|
||||
var resumedSession = FileUploader.UploadChunk<string>(request.UploadId, request.ChunkStream, request.ChunkSize);
|
||||
var resumedSession = FileUploader.UploadChunk<T>(request.UploadId, request.ChunkStream, request.ChunkSize);
|
||||
|
||||
if (resumedSession.BytesUploaded == resumedSession.BytesTotal)
|
||||
{
|
||||
WriteSuccess(context, ToResponseObject(resumedSession.File), (int)HttpStatusCode.Created);
|
||||
await WriteSuccess(context, ToResponseObject(resumedSession.File), (int)HttpStatusCode.Created);
|
||||
FilesMessageService.Send(resumedSession.File, MessageAction.FileUploaded, resumedSession.File.Title);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteSuccess(context, ChunkedUploadSessionHelper.ToResponseObject(resumedSession));
|
||||
await WriteSuccess(context, ChunkedUploadSessionHelper.ToResponseObject(resumedSession));
|
||||
}
|
||||
return;
|
||||
|
||||
default:
|
||||
WriteError(context, "Unknown request type.");
|
||||
await WriteError(context, "Unknown request type.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException error)
|
||||
{
|
||||
Logger.Error(error);
|
||||
WriteError(context, FilesCommonResource.ErrorMassage_FileNotFound);
|
||||
await WriteError(context, FilesCommonResource.ErrorMassage_FileNotFound);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
Logger.Error(error);
|
||||
WriteError(context, error.Message);
|
||||
await WriteError(context, error.Message);
|
||||
}
|
||||
}
|
||||
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
|
||||
private bool TryAuthorize(ChunkedRequestHelper request)
|
||||
private bool TryAuthorize<T>(ChunkedRequestHelper<T> request)
|
||||
{
|
||||
if (request.Type(InstanceCrypto) == ChunkedRequestType.Initiate)
|
||||
{
|
||||
@ -172,7 +202,7 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
|
||||
if (!string.IsNullOrEmpty(request.UploadId))
|
||||
{
|
||||
var uploadSession = ChunkedUploadSessionHolder.GetSession<string>(request.UploadId);
|
||||
var uploadSession = ChunkedUploadSessionHolder.GetSession(request.UploadId);
|
||||
if (uploadSession != null)
|
||||
{
|
||||
TenantManager.SetCurrentTenant(uploadSession.TenantId);
|
||||
@ -187,21 +217,21 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void WriteError(HttpContext context, string message)
|
||||
private static async Task WriteError(HttpContext context, string message)
|
||||
{
|
||||
WriteResponse(context, false, null, message, (int)HttpStatusCode.OK);
|
||||
await WriteResponse(context, false, null, message, (int)HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
private static void WriteSuccess(HttpContext context, object data, int statusCode = (int)HttpStatusCode.OK)
|
||||
private static async Task WriteSuccess(HttpContext context, object data, int statusCode = (int)HttpStatusCode.OK)
|
||||
{
|
||||
WriteResponse(context, true, data, string.Empty, statusCode);
|
||||
await WriteResponse(context, true, data, string.Empty, statusCode);
|
||||
}
|
||||
|
||||
private static void WriteResponse(HttpContext context, bool success, object data, string message, int statusCode)
|
||||
private static async Task WriteResponse(HttpContext context, bool success, object data, string message, int statusCode)
|
||||
{
|
||||
context.Response.StatusCode = statusCode;
|
||||
context.Response.WriteAsync(JsonConvert.SerializeObject(new { success, data, message })).Wait();
|
||||
context.Response.ContentType = "application/json";
|
||||
await context.Response.WriteAsync(JsonConvert.SerializeObject(new { success, data, message }));
|
||||
}
|
||||
|
||||
private static object ToResponseObject<T>(File<T> file)
|
||||
@ -216,8 +246,9 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
uploaded = true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private enum ChunkedRequestType
|
||||
public enum ChunkedRequestType
|
||||
{
|
||||
None,
|
||||
Initiate,
|
||||
@ -226,7 +257,7 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{Type} ({UploadId})")]
|
||||
private class ChunkedRequestHelper
|
||||
public class ChunkedRequestHelper<T>
|
||||
{
|
||||
private readonly HttpRequest _request;
|
||||
private IFormFile _file;
|
||||
@ -279,14 +310,14 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
return _authKey.Value;
|
||||
}
|
||||
|
||||
public string FolderId
|
||||
public T FolderId
|
||||
{
|
||||
get { return _request.Query[FilesLinkUtility.FolderId]; }
|
||||
get { return (T)Convert.ChangeType(_request.Query[FilesLinkUtility.FolderId], typeof(T)); }
|
||||
}
|
||||
|
||||
public string FileId
|
||||
public T FileId
|
||||
{
|
||||
get { return _request.Query[FilesLinkUtility.FileId]; }
|
||||
get { return (T)Convert.ChangeType(_request.Query[FilesLinkUtility.FileId], typeof(T)); }
|
||||
}
|
||||
|
||||
public string FileName
|
||||
@ -359,8 +390,7 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
|
||||
private bool IsFileDataSet()
|
||||
{
|
||||
return !string.IsNullOrEmpty(FileName) && !string.IsNullOrEmpty(FolderId);
|
||||
}
|
||||
return !string.IsNullOrEmpty(FileName) && !FolderId.Equals(default(T));
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,7 +398,7 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
{
|
||||
public static DIHelper AddChunkedUploaderHandlerService(this DIHelper services)
|
||||
{
|
||||
services.TryAddScoped<ChunkedUploaderHandler>();
|
||||
services.TryAddScoped<ChunkedUploaderHandlerService>();
|
||||
return services
|
||||
.AddTenantManagerService()
|
||||
.AddFileUploaderService()
|
||||
@ -381,5 +411,10 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
.AddChunkedUploadSessionHolderService()
|
||||
.AddChunkedUploadSessionHelperService();
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseChunkedUploaderHandler(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<ChunkedUploaderHandler>();
|
||||
}
|
||||
}
|
||||
}
|
@ -34,10 +34,12 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
using ASC.Common;
|
||||
using ASC.Common.Logging;
|
||||
using ASC.Common.Web;
|
||||
using ASC.Core;
|
||||
using ASC.Files.Core;
|
||||
using ASC.Files.Core.Data;
|
||||
using ASC.Files.Core.Security;
|
||||
using ASC.Files.Resources;
|
||||
using ASC.MessagingSystem;
|
||||
@ -56,27 +58,45 @@ using ASC.Web.Studio.Utility;
|
||||
|
||||
using JWT;
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
using FileShare = ASC.Files.Core.Security.FileShare;
|
||||
using MimeMapping = ASC.Common.Web.MimeMapping;
|
||||
using SecurityContext = ASC.Core.SecurityContext;
|
||||
|
||||
namespace ASC.Web.Files
|
||||
{
|
||||
public class FileHandler //: AbstractHttpAsyncHandler
|
||||
public class FileHandler
|
||||
{
|
||||
public RequestDelegate Next { get; }
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
public FileHandler(RequestDelegate next, IServiceProvider serviceProvider)
|
||||
{
|
||||
Next = next;
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
using var scope = ServiceProvider.CreateScope();
|
||||
var fileHandlerService = scope.ServiceProvider.GetService<FileHandlerService>();
|
||||
await fileHandlerService.Invoke(context);
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
}
|
||||
|
||||
public class FileHandlerService
|
||||
{
|
||||
public string FileHandlerPath
|
||||
{
|
||||
get { return FilesLinkUtility.FileHandlerPath; }
|
||||
}
|
||||
|
||||
public RequestDelegate Next { get; }
|
||||
public FilesLinkUtility FilesLinkUtility { get; }
|
||||
public TenantExtra TenantExtra { get; }
|
||||
public AuthContext AuthContext { get; }
|
||||
@ -103,8 +123,7 @@ namespace ASC.Web.Files
|
||||
public CookiesManager CookiesManager { get; }
|
||||
public TenantStatisticsProvider TenantStatisticsProvider { get; }
|
||||
|
||||
public FileHandler(
|
||||
RequestDelegate next,
|
||||
public FileHandlerService(
|
||||
FilesLinkUtility filesLinkUtility,
|
||||
TenantExtra tenantExtra,
|
||||
CookiesManager cookiesManager,
|
||||
@ -130,7 +149,6 @@ namespace ASC.Web.Files
|
||||
FFmpegService fFmpegService,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
Next = next;
|
||||
FilesLinkUtility = filesLinkUtility;
|
||||
TenantExtra = tenantExtra;
|
||||
AuthContext = authContext;
|
||||
@ -172,31 +190,31 @@ namespace ASC.Web.Files
|
||||
{
|
||||
case "view":
|
||||
case "download":
|
||||
DownloadFile(context);
|
||||
await DownloadFile(context);
|
||||
break;
|
||||
case "bulk":
|
||||
BulkDownloadFile(context);
|
||||
await BulkDownloadFile(context);
|
||||
break;
|
||||
case "stream":
|
||||
StreamFile(context);
|
||||
await StreamFile(context);
|
||||
break;
|
||||
case "empty":
|
||||
EmptyFile(context);
|
||||
await EmptyFile(context);
|
||||
break;
|
||||
case "tmp":
|
||||
TempFile(context);
|
||||
await TempFile(context);
|
||||
break;
|
||||
case "create":
|
||||
CreateFile(context);
|
||||
await CreateFile(context);
|
||||
break;
|
||||
case "redirect":
|
||||
Redirect(context);
|
||||
break;
|
||||
case "diff":
|
||||
DifferenceFile(context);
|
||||
await DifferenceFile(context);
|
||||
break;
|
||||
case "track":
|
||||
TrackFile(context);
|
||||
await TrackFile(context);
|
||||
break;
|
||||
default:
|
||||
throw new HttpException((int)HttpStatusCode.BadRequest, FilesCommonResource.ErrorMassage_BadRequest);
|
||||
@ -207,11 +225,9 @@ namespace ASC.Web.Files
|
||||
{
|
||||
throw new HttpException((int)HttpStatusCode.InternalServerError, FilesCommonResource.ErrorMassage_BadRequest, e);
|
||||
}
|
||||
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
|
||||
private void BulkDownloadFile(HttpContext context)
|
||||
private async Task BulkDownloadFile(HttpContext context)
|
||||
{
|
||||
if (!SecurityContext.AuthenticateMe(CookiesManager.GetCookies(CookiesType.AuthKey)))
|
||||
{
|
||||
@ -250,10 +266,10 @@ namespace ASC.Web.Files
|
||||
readStream.Seek(offset, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
SendStreamByChunks(context, length, FileConstant.DownloadTitle + ".zip", readStream, ref flushed);
|
||||
flushed = await SendStreamByChunksAsync(context, length, FileConstant.DownloadTitle + ".zip", readStream, flushed);
|
||||
}
|
||||
|
||||
context.Response.Body.Flush();
|
||||
await context.Response.Body.FlushAsync();
|
||||
//context.Response.SuppressContent = true;
|
||||
//context.ApplicationInstance.CompleteRequest();
|
||||
}
|
||||
@ -264,21 +280,21 @@ namespace ASC.Web.Files
|
||||
}
|
||||
}
|
||||
|
||||
private void DownloadFile(HttpContext context)
|
||||
private async Task DownloadFile(HttpContext context)
|
||||
{
|
||||
var q = context.Request.Query[FilesLinkUtility.FileId];
|
||||
|
||||
if (int.TryParse(q, out var id))
|
||||
{
|
||||
DownloadFile(context, id);
|
||||
await DownloadFile(context, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
DownloadFile(context, q);
|
||||
await DownloadFile(context, q);
|
||||
}
|
||||
}
|
||||
|
||||
private void DownloadFile<T>(HttpContext context, T id)
|
||||
private async Task DownloadFile<T>(HttpContext context, T id)
|
||||
{
|
||||
var flushed = false;
|
||||
try
|
||||
@ -415,7 +431,7 @@ namespace ASC.Web.Files
|
||||
}
|
||||
}
|
||||
|
||||
SendStreamByChunks(context, length, title, fileStream, ref flushed);
|
||||
flushed = await SendStreamByChunksAsync(context, length, title, fileStream, flushed);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -436,7 +452,7 @@ namespace ASC.Web.Files
|
||||
fileStream.Seek(offset, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
SendStreamByChunks(context, length, title, fileStream, ref flushed);
|
||||
flushed = await SendStreamByChunksAsync(context, length, title, fileStream, flushed);
|
||||
}
|
||||
}
|
||||
catch (ThreadAbortException tae)
|
||||
@ -453,13 +469,13 @@ namespace ASC.Web.Files
|
||||
if (fileStream != null)
|
||||
{
|
||||
fileStream.Close();
|
||||
fileStream.Dispose();
|
||||
await fileStream.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
context.Response.Body.Flush();
|
||||
await context.Response.Body.FlushAsync();
|
||||
//context.Response.SuppressContent = true;
|
||||
//context.ApplicationInstance.CompleteRequest();
|
||||
flushed = true;
|
||||
@ -487,7 +503,7 @@ namespace ASC.Web.Files
|
||||
if (!flushed && !context.RequestAborted.IsCancellationRequested)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
context.Response.WriteAsync(HttpUtility.HtmlEncode(ex.Message)).Wait();
|
||||
await context.Response.WriteAsync(HttpUtility.HtmlEncode(ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -525,7 +541,7 @@ namespace ASC.Web.Files
|
||||
return length;
|
||||
}
|
||||
|
||||
private void SendStreamByChunks(HttpContext context, long toRead, string title, Stream fileStream, ref bool flushed)
|
||||
private async Task<bool> SendStreamByChunksAsync(HttpContext context, long toRead, string title, Stream fileStream, bool flushed)
|
||||
{
|
||||
//context.Response.Buffer = false;
|
||||
context.Response.Headers.Add("Connection", "Keep-Alive");
|
||||
@ -541,8 +557,8 @@ namespace ASC.Web.Files
|
||||
|
||||
if (!context.RequestAborted.IsCancellationRequested)
|
||||
{
|
||||
context.Response.Body.Write(buffer, 0, length);
|
||||
context.Response.Body.Flush();
|
||||
await context.Response.Body.WriteAsync(buffer, 0, length);
|
||||
await context.Response.Body.FlushAsync();
|
||||
flushed = true;
|
||||
toRead -= length;
|
||||
}
|
||||
@ -552,23 +568,25 @@ namespace ASC.Web.Files
|
||||
Logger.Warn(string.Format("IsClientConnected is false. Why? Download file {0} Connection is lost. ", title));
|
||||
}
|
||||
}
|
||||
|
||||
return flushed;
|
||||
}
|
||||
|
||||
private void StreamFile(HttpContext context)
|
||||
private async Task StreamFile(HttpContext context)
|
||||
{
|
||||
var q = context.Request.Query[FilesLinkUtility.FileId];
|
||||
|
||||
if (int.TryParse(q, out var id))
|
||||
{
|
||||
StreamFile(context, id);
|
||||
await StreamFile(context, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
StreamFile(context, q);
|
||||
await StreamFile(context, q);
|
||||
}
|
||||
}
|
||||
|
||||
private void StreamFile<T>(HttpContext context, T id)
|
||||
private async Task StreamFile<T>(HttpContext context, T id)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -593,7 +611,7 @@ namespace ASC.Web.Files
|
||||
Logger.Error($"{FilesLinkUtility.AuthKey} {validateResult}: {context.Request.Url()}", exc);
|
||||
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
context.Response.WriteAsync(FilesCommonResource.ErrorMassage_SecurityException).Wait();
|
||||
await context.Response.WriteAsync(FilesCommonResource.ErrorMassage_SecurityException);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -606,9 +624,8 @@ namespace ASC.Web.Files
|
||||
{
|
||||
throw new Exception("Invalid header " + header);
|
||||
}
|
||||
header = header.Substring("Bearer ".Length);
|
||||
|
||||
JsonWebToken.JsonSerializer = new DocumentService.JwtSerializer();
|
||||
header = header.Substring("Bearer ".Length);
|
||||
|
||||
var stringPayload = JsonWebToken.Decode(header, FileUtility.SignatureSecret);
|
||||
|
||||
@ -636,7 +653,7 @@ namespace ASC.Web.Files
|
||||
{
|
||||
Logger.Error("Download stream header " + context.Request.Url(), ex);
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
context.Response.WriteAsync(FilesCommonResource.ErrorMassage_SecurityException).Wait();
|
||||
await context.Response.WriteAsync(FilesCommonResource.ErrorMassage_SecurityException);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -664,7 +681,7 @@ namespace ASC.Web.Files
|
||||
|
||||
if (!string.IsNullOrEmpty(file.Error))
|
||||
{
|
||||
context.Response.WriteAsync(file.Error).Wait();
|
||||
await context.Response.WriteAsync(file.Error);
|
||||
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||
return;
|
||||
}
|
||||
@ -672,26 +689,24 @@ namespace ASC.Web.Files
|
||||
context.Response.Headers.Add("Content-Disposition", ContentDispositionUtil.GetHeaderValue(file.Title));
|
||||
context.Response.ContentType = MimeMapping.GetMimeMapping(file.Title);
|
||||
|
||||
using (var stream = fileDao.GetFileStream(file))
|
||||
{
|
||||
using var stream = fileDao.GetFileStream(file);
|
||||
context.Response.Headers.Add("Content-Length",
|
||||
stream.CanSeek
|
||||
? stream.Length.ToString(CultureInfo.InvariantCulture)
|
||||
: file.ContentLength.ToString(CultureInfo.InvariantCulture));
|
||||
stream.StreamCopyTo(context.Response.Body);
|
||||
}
|
||||
await stream.CopyToAsync(context.Response.Body, StreamExtension.BufferSize);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Error for: " + context.Request.Url(), ex);
|
||||
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||
context.Response.WriteAsync(ex.Message).Wait();
|
||||
await context.Response.WriteAsync(ex.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
context.Response.Body.Flush();
|
||||
await context.Response.Body.FlushAsync();
|
||||
//context.Response.SuppressContent = true;
|
||||
//context.ApplicationInstance.CompleteRequest();
|
||||
}
|
||||
@ -701,7 +716,7 @@ namespace ASC.Web.Files
|
||||
}
|
||||
}
|
||||
|
||||
private void EmptyFile(HttpContext context)
|
||||
private async Task EmptyFile(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -715,9 +730,8 @@ namespace ASC.Web.Files
|
||||
{
|
||||
throw new Exception("Invalid header " + header);
|
||||
}
|
||||
header = header.Substring("Bearer ".Length);
|
||||
|
||||
JsonWebToken.JsonSerializer = new DocumentService.JwtSerializer();
|
||||
header = header.Substring("Bearer ".Length);
|
||||
|
||||
var stringPayload = JsonWebToken.Decode(header, FileUtility.SignatureSecret);
|
||||
|
||||
@ -745,7 +759,7 @@ namespace ASC.Web.Files
|
||||
{
|
||||
Logger.Error("Download stream header " + context.Request.Url(), ex);
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
context.Response.WriteAsync(FilesCommonResource.ErrorMassage_SecurityException).Wait();
|
||||
await context.Response.WriteAsync(FilesCommonResource.ErrorMassage_SecurityException);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -761,7 +775,7 @@ namespace ASC.Web.Files
|
||||
if (!storeTemplate.IsFile("", path))
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
context.Response.WriteAsync(FilesCommonResource.ErrorMassage_FileNotFound).Wait();
|
||||
await context.Response.WriteAsync(FilesCommonResource.ErrorMassage_FileNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -779,13 +793,13 @@ namespace ASC.Web.Files
|
||||
{
|
||||
Logger.Error("Error for: " + context.Request.Url(), ex);
|
||||
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||
context.Response.WriteAsync(ex.Message).Wait();
|
||||
await context.Response.WriteAsync(ex.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
context.Response.Body.Flush();
|
||||
await context.Response.Body.FlushAsync();
|
||||
//context.Response.SuppressContent = true;
|
||||
//context.ApplicationInstance.CompleteRequest();
|
||||
}
|
||||
@ -795,7 +809,7 @@ namespace ASC.Web.Files
|
||||
}
|
||||
}
|
||||
|
||||
private void TempFile(HttpContext context)
|
||||
private async Task TempFile(HttpContext context)
|
||||
{
|
||||
var fileName = context.Request.Query[FilesLinkUtility.FileTitle];
|
||||
var auth = context.Request.Query[FilesLinkUtility.AuthKey].FirstOrDefault();
|
||||
@ -808,7 +822,7 @@ namespace ASC.Web.Files
|
||||
Logger.Error($"{FilesLinkUtility.AuthKey} {validateResult}: {context.Request.Url()}", exc);
|
||||
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
context.Response.WriteAsync(FilesCommonResource.ErrorMassage_SecurityException);
|
||||
await context.Response.WriteAsync(FilesCommonResource.ErrorMassage_SecurityException);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -823,21 +837,21 @@ namespace ASC.Web.Files
|
||||
if (!store.IsFile(FileConstant.StorageDomainTmp, path))
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
context.Response.WriteAsync(FilesCommonResource.ErrorMassage_FileNotFound).Wait();
|
||||
await context.Response.WriteAsync(FilesCommonResource.ErrorMassage_FileNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var readStream = store.GetReadStream(FileConstant.StorageDomainTmp, path))
|
||||
{
|
||||
context.Response.Headers.Add("Content-Length", readStream.Length.ToString(CultureInfo.InvariantCulture));
|
||||
readStream.StreamCopyTo(context.Response.Body);
|
||||
await readStream.CopyToAsync(context.Response.Body, StreamExtension.BufferSize);
|
||||
}
|
||||
|
||||
store.Delete(FileConstant.StorageDomainTmp, path);
|
||||
|
||||
try
|
||||
{
|
||||
context.Response.Body.Flush();
|
||||
await context.Response.Body.FlushAsync();
|
||||
//context.Response.SuppressContent = true;
|
||||
//context.ApplicationInstance.CompleteRequest();
|
||||
}
|
||||
@ -847,21 +861,21 @@ namespace ASC.Web.Files
|
||||
}
|
||||
}
|
||||
|
||||
private void DifferenceFile(HttpContext context)
|
||||
private async Task DifferenceFile(HttpContext context)
|
||||
{
|
||||
var q = context.Request.Query[FilesLinkUtility.FileId];
|
||||
|
||||
if (int.TryParse(q, out var id))
|
||||
{
|
||||
DifferenceFile(context, id);
|
||||
await DifferenceFile(context, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
DifferenceFile(context, q);
|
||||
await DifferenceFile(context, q);
|
||||
}
|
||||
}
|
||||
|
||||
private void DifferenceFile<T>(HttpContext context, T id)
|
||||
private async Task DifferenceFile<T>(HttpContext context, T id)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -881,7 +895,7 @@ namespace ASC.Web.Files
|
||||
Logger.Error($"{FilesLinkUtility.AuthKey} {validateResult}: {context.Request.Url()}", exc);
|
||||
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
context.Response.WriteAsync(FilesCommonResource.ErrorMassage_SecurityException).Wait();
|
||||
await context.Response.WriteAsync(FilesCommonResource.ErrorMassage_SecurityException);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -910,7 +924,7 @@ namespace ASC.Web.Files
|
||||
|
||||
if (!string.IsNullOrEmpty(file.Error))
|
||||
{
|
||||
context.Response.WriteAsync(file.Error).Wait();
|
||||
await context.Response.WriteAsync(file.Error);
|
||||
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||
return;
|
||||
}
|
||||
@ -921,20 +935,20 @@ namespace ASC.Web.Files
|
||||
using (var stream = fileDao.GetDifferenceStream(file))
|
||||
{
|
||||
context.Response.Headers.Add("Content-Length", stream.Length.ToString(CultureInfo.InvariantCulture));
|
||||
stream.StreamCopyTo(context.Response.Body);
|
||||
await stream.CopyToAsync(context.Response.Body, StreamExtension.BufferSize);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||
context.Response.WriteAsync(ex.Message).Wait();
|
||||
await context.Response.WriteAsync(ex.Message);
|
||||
Logger.Error("Error for: " + context.Request.Url(), ex);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
context.Response.Body.Flush();
|
||||
await context.Response.Body.FlushAsync();
|
||||
//context.Response.SuppressContent = true;
|
||||
//context.ApplicationInstance.CompleteRequest();
|
||||
}
|
||||
@ -949,20 +963,20 @@ namespace ASC.Web.Files
|
||||
return file.ID + ":" + file.Version + ":" + file.Title.GetHashCode() + ":" + file.ContentLength;
|
||||
}
|
||||
|
||||
private void CreateFile(HttpContext context)
|
||||
private async Task CreateFile(HttpContext context)
|
||||
{
|
||||
var folderId = context.Request.Query[FilesLinkUtility.FolderId].FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(folderId))
|
||||
{
|
||||
CreateFile(context, GlobalFolderHelper.FolderMy);
|
||||
await CreateFile(context, GlobalFolderHelper.FolderMy);
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateFile(context, folderId);
|
||||
await CreateFile(context, folderId);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateFile<T>(HttpContext context, T folderId)
|
||||
private async Task CreateFile<T>(HttpContext context, T folderId)
|
||||
{
|
||||
var responseMessage = context.Request.Query["response"] == "message";
|
||||
Folder<T> folder;
|
||||
@ -993,7 +1007,7 @@ namespace ASC.Web.Files
|
||||
Logger.Error(ex);
|
||||
if (responseMessage)
|
||||
{
|
||||
context.Response.WriteAsync("error: " + ex.Message).Wait();
|
||||
await context.Response.WriteAsync("error: " + ex.Message);
|
||||
return;
|
||||
}
|
||||
context.Response.Redirect(PathProvider.StartURL + "#error/" + HttpUtility.UrlEncode(ex.Message), true);
|
||||
@ -1004,7 +1018,7 @@ namespace ASC.Web.Files
|
||||
|
||||
if (responseMessage)
|
||||
{
|
||||
context.Response.WriteAsync("ok: " + string.Format(FilesCommonResource.MessageFileCreated, folder.Title)).Wait();
|
||||
await context.Response.WriteAsync("ok: " + string.Format(FilesCommonResource.MessageFileCreated, folder.Title));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1134,7 +1148,7 @@ namespace ASC.Web.Files
|
||||
context.Response.Redirect(urlRedirect);
|
||||
}
|
||||
|
||||
private void TrackFile(HttpContext context)
|
||||
private async Task TrackFile(HttpContext context)
|
||||
{
|
||||
var auth = context.Request.Query[FilesLinkUtility.AuthKey].FirstOrDefault();
|
||||
var fileId = context.Request.Query[FilesLinkUtility.FileId].FirstOrDefault();
|
||||
@ -1177,7 +1191,6 @@ namespace ASC.Web.Files
|
||||
|
||||
if (!string.IsNullOrEmpty(FileUtility.SignatureSecret))
|
||||
{
|
||||
JsonWebToken.JsonSerializer = new DocumentService.JwtSerializer();
|
||||
if (!string.IsNullOrEmpty(fileData.Token))
|
||||
{
|
||||
try
|
||||
@ -1240,7 +1253,43 @@ namespace ASC.Web.Files
|
||||
}
|
||||
result ??= new DocumentServiceTracker.TrackResponse();
|
||||
|
||||
context.Response.WriteAsync(DocumentServiceTracker.TrackResponse.Serialize(result)).Wait();
|
||||
await context.Response.WriteAsync(DocumentServiceTracker.TrackResponse.Serialize(result));
|
||||
}
|
||||
}
|
||||
|
||||
public static class FileHandlerExtensions
|
||||
{
|
||||
public static DIHelper AddFileHandlerService(this DIHelper services)
|
||||
{
|
||||
services.TryAddScoped<FileHandlerService>();
|
||||
return services
|
||||
.AddFilesLinkUtilityService()
|
||||
.AddTenantExtraService()
|
||||
.AddCookiesManagerService()
|
||||
.AddAuthContextService()
|
||||
.AddSecurityContextService()
|
||||
.AddGlobalStoreService()
|
||||
.AddDaoFactoryService()
|
||||
.AddFileSecurityService()
|
||||
.AddFileMarkerService()
|
||||
.AddSetupInfo()
|
||||
.AddFileUtilityService()
|
||||
.AddGlobalService()
|
||||
.AddEmailValidationKeyProviderService()
|
||||
.AddCoreBaseSettingsService()
|
||||
.AddGlobalFolderHelperService()
|
||||
.AddPathProviderService()
|
||||
.AddUserManagerService()
|
||||
.AddDocumentServiceTrackerHelperService()
|
||||
.AddFilesMessageService()
|
||||
.AddFileConverterService()
|
||||
.AddFileShareLinkService()
|
||||
.AddFFmpegServiceService();
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseFileHandler(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<FileHandler>();
|
||||
}
|
||||
}
|
||||
}
|
@ -30,6 +30,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
using ASC.Common;
|
||||
using ASC.Common.Logging;
|
||||
using ASC.Core;
|
||||
using ASC.Core.Common;
|
||||
@ -37,37 +38,54 @@ using ASC.Files.Resources;
|
||||
using ASC.Web.Files.ThirdPartyApp;
|
||||
using ASC.Web.Studio.Utility;
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ASC.Web.Files.HttpHandlers
|
||||
{
|
||||
public class ThirdPartyAppHandler //: IHttpHandler
|
||||
public class ThirdPartyAppHandler
|
||||
{
|
||||
public RequestDelegate Next { get; }
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
public ThirdPartyAppHandler(RequestDelegate next, IServiceProvider serviceProvider)
|
||||
{
|
||||
Next = next;
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
using var scope = ServiceProvider.CreateScope();
|
||||
var thirdPartyAppHandlerService = scope.ServiceProvider.GetService<ThirdPartyAppHandlerService>();
|
||||
thirdPartyAppHandlerService.Invoke(context);
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
}
|
||||
|
||||
public class ThirdPartyAppHandlerService
|
||||
{
|
||||
public AuthContext AuthContext { get; }
|
||||
public BaseCommonLinkUtility BaseCommonLinkUtility { get; }
|
||||
public CommonLinkUtility CommonLinkUtility { get; }
|
||||
private ILog Log { get; set; }
|
||||
|
||||
public string HandlerPath { get; set; }
|
||||
|
||||
public ThirdPartyAppHandler(
|
||||
RequestDelegate next,
|
||||
public ThirdPartyAppHandlerService(
|
||||
IOptionsMonitor<ILog> optionsMonitor,
|
||||
AuthContext authContext,
|
||||
BaseCommonLinkUtility baseCommonLinkUtility,
|
||||
CommonLinkUtility commonLinkUtility)
|
||||
{
|
||||
Next = next;
|
||||
AuthContext = authContext;
|
||||
BaseCommonLinkUtility = baseCommonLinkUtility;
|
||||
CommonLinkUtility = commonLinkUtility;
|
||||
Log = optionsMonitor.CurrentValue;
|
||||
HandlerPath = baseCommonLinkUtility.ToAbsolute("~/thirdpartyapp");
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
public void Invoke(HttpContext context)
|
||||
{
|
||||
Log.Debug("ThirdPartyApp: handler request - " + context.Request.Url());
|
||||
|
||||
@ -80,13 +98,11 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
|
||||
if (app.Request(context))
|
||||
{
|
||||
await Next.Invoke(context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (ThreadAbortException)
|
||||
{
|
||||
await Next.Invoke(context);
|
||||
//Thats is responce ending
|
||||
return;
|
||||
}
|
||||
@ -111,7 +127,23 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
redirectUrl += HttpUtility.UrlEncode(message);
|
||||
}
|
||||
context.Response.Redirect(redirectUrl, true);
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ThirdPartyAppHandlerExtention
|
||||
{
|
||||
public static DIHelper AddThirdPartyAppHandlerService(this DIHelper services)
|
||||
{
|
||||
services.TryAddScoped<ThirdPartyAppHandlerService>();
|
||||
return services
|
||||
.AddCommonLinkUtilityService()
|
||||
.AddBaseCommonLinkUtilityService()
|
||||
.AddAuthContextService();
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseThirdPartyAppHandler(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<ThirdPartyAppHandler>();
|
||||
}
|
||||
}
|
||||
}
|
@ -42,12 +42,34 @@ using ASC.Web.Files.Helpers;
|
||||
using ASC.Web.Files.Services.NotifyService;
|
||||
using ASC.Web.Studio.Utility;
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ASC.Web.Files.HttpHandlers
|
||||
{
|
||||
public class DocuSignHandler
|
||||
{
|
||||
public RequestDelegate Next { get; }
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
public DocuSignHandler(RequestDelegate next, IServiceProvider serviceProvider)
|
||||
{
|
||||
Next = next;
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
using var scope = ServiceProvider.CreateScope();
|
||||
var docuSignHandlerService = scope.ServiceProvider.GetService<DocuSignHandlerService>();
|
||||
await docuSignHandlerService.Invoke(context);
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
}
|
||||
|
||||
public class DocuSignHandlerService
|
||||
{
|
||||
public static string Path(FilesLinkUtility filesLinkUtility)
|
||||
{
|
||||
@ -55,23 +77,18 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
}
|
||||
|
||||
private ILog Log { get; set; }
|
||||
|
||||
public RequestDelegate Next { get; }
|
||||
|
||||
public TenantExtra TenantExtra { get; }
|
||||
public DocuSignHelper DocuSignHelper { get; }
|
||||
public SecurityContext SecurityContext { get; }
|
||||
public NotifyClient NotifyClient { get; }
|
||||
|
||||
public DocuSignHandler(
|
||||
RequestDelegate next,
|
||||
public DocuSignHandlerService(
|
||||
IOptionsMonitor<ILog> optionsMonitor,
|
||||
TenantExtra tenantExtra,
|
||||
DocuSignHelper docuSignHelper,
|
||||
SecurityContext securityContext,
|
||||
NotifyClient notifyClient)
|
||||
{
|
||||
Next = next;
|
||||
TenantExtra = tenantExtra;
|
||||
DocuSignHelper = docuSignHelper;
|
||||
SecurityContext = securityContext;
|
||||
@ -84,7 +101,7 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
if (TenantExtra.IsNotPaid())
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.PaymentRequired;
|
||||
context.Response.WriteAsync("Payment Required.").Wait();
|
||||
await context.Response.WriteAsync("Payment Required.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -106,8 +123,6 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
{
|
||||
throw new HttpException((int)HttpStatusCode.InternalServerError, FilesCommonResource.ErrorMassage_BadRequest, e);
|
||||
}
|
||||
|
||||
await Next.Invoke(context);
|
||||
}
|
||||
|
||||
private void Redirect(HttpContext context)
|
||||
@ -241,7 +256,7 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
{
|
||||
public static DIHelper AddDocuSignHandlerService(this DIHelper services)
|
||||
{
|
||||
services.TryAddScoped<DocuSignHandler>();
|
||||
services.TryAddScoped<DocuSignHandlerService>();
|
||||
return services
|
||||
.AddFilesLinkUtilityService()
|
||||
.AddTenantExtraService()
|
||||
@ -249,5 +264,10 @@ namespace ASC.Web.Files.HttpHandlers
|
||||
.AddSecurityContextService()
|
||||
.AddNotifyClientService();
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseDocuSignHandler(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<DocuSignHandler>();
|
||||
}
|
||||
}
|
||||
}
|
10
products/ASC.Files/Server/Model/SessionModel.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace ASC.Files.Model
|
||||
{
|
||||
public class SessionModel
|
||||
{
|
||||
public string FileName { get; set; }
|
||||
public long FileSize { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public bool Encrypted { get; set; }
|
||||
}
|
||||
}
|
@ -43,8 +43,6 @@ using ASC.Web.Files.Classes;
|
||||
using ASC.Web.Files.Utils;
|
||||
using ASC.Web.Studio.Core;
|
||||
|
||||
using JWT;
|
||||
|
||||
using FileShare = ASC.Files.Core.Security.FileShare;
|
||||
|
||||
namespace ASC.Web.Files.Services.DocumentService
|
||||
@ -293,8 +291,7 @@ namespace ASC.Web.Files.Services.DocumentService
|
||||
{
|
||||
if (string.IsNullOrEmpty(FileUtility.SignatureSecret)) return null;
|
||||
|
||||
JsonWebToken.JsonSerializer = new Web.Core.Files.DocumentService.JwtSerializer();
|
||||
return JsonWebToken.Encode(payload, FileUtility.SignatureSecret, JwtHashAlgorithm.HS256);
|
||||
return JsonWebToken.Encode(payload, FileUtility.SignatureSecret);
|
||||
}
|
||||
|
||||
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using ASC.Common;
|
||||
using ASC.Common.Logging;
|
||||
using ASC.Core;
|
||||
|
||||
@ -53,7 +54,7 @@ namespace ASC.Web.Files.Services.FFmpegService
|
||||
public FFmpegService(IOptionsMonitor<ILog> optionsMonitor, IConfiguration configuration)
|
||||
{
|
||||
logger = optionsMonitor.CurrentValue;
|
||||
FFmpegPath = configuration["files:ffmpeg"];
|
||||
FFmpegPath = configuration["files:ffmpeg:value"];
|
||||
FFmpegArgs = configuration["files:ffmpeg:args"] ?? "-i - -preset ultrafast -movflags frag_keyframe+empty_moov -f {0} -";
|
||||
|
||||
var exts = configuration["files:ffmpeg:exts"];
|
||||
@ -169,4 +170,12 @@ namespace ASC.Web.Files.Services.FFmpegService
|
||||
}
|
||||
}
|
||||
}
|
||||
public static class FFmpegServiceExtensions
|
||||
{
|
||||
public static DIHelper AddFFmpegServiceService(this DIHelper services)
|
||||
{
|
||||
services.TryAddSingleton<FFmpegService>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,8 @@ using ASC.Api.Documents;
|
||||
using ASC.Common;
|
||||
using ASC.Common.DependencyInjection;
|
||||
using ASC.Common.Logging;
|
||||
using ASC.Web.Files;
|
||||
using ASC.Web.Files.HttpHandlers;
|
||||
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@ -81,7 +83,11 @@ namespace ASC.Files
|
||||
|
||||
diHelper
|
||||
.AddDocumentsControllerService()
|
||||
.AddEncryptionControllerService();
|
||||
.AddEncryptionControllerService()
|
||||
.AddFileHandlerService()
|
||||
.AddChunkedUploaderHandlerService()
|
||||
.AddThirdPartyAppHandlerService()
|
||||
.AddDocuSignHandlerService();
|
||||
|
||||
services.AddAutofac(Configuration, HostEnvironment.ContentRootPath);
|
||||
}
|
||||
@ -120,6 +126,34 @@ namespace ASC.Files
|
||||
endpoints.MapCustom();
|
||||
});
|
||||
|
||||
app.MapWhen(
|
||||
context => context.Request.Path.ToString().EndsWith("httphandlers/filehandler.ashx"),
|
||||
appBranch =>
|
||||
{
|
||||
appBranch.UseFileHandler();
|
||||
});
|
||||
|
||||
app.MapWhen(
|
||||
context => context.Request.Path.ToString().EndsWith("ChunkedUploader.ashx"),
|
||||
appBranch =>
|
||||
{
|
||||
appBranch.UseChunkedUploaderHandler();
|
||||
});
|
||||
|
||||
app.MapWhen(
|
||||
context => context.Request.Path.ToString().EndsWith("ThirdPartyAppHandler.ashx"),
|
||||
appBranch =>
|
||||
{
|
||||
appBranch.UseThirdPartyAppHandler();
|
||||
});
|
||||
|
||||
app.MapWhen(
|
||||
context => context.Request.Path.ToString().EndsWith("DocuSignHandler.ashx"),
|
||||
appBranch =>
|
||||
{
|
||||
appBranch.UseDocuSignHandler();
|
||||
});
|
||||
|
||||
app.UseStaticFiles();
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ namespace ASC.Web.Files.ThirdPartyApp
|
||||
public SetupInfo SetupInfo { get; }
|
||||
public TokenHelper TokenHelper { get; }
|
||||
public DocumentServiceConnector DocumentServiceConnector { get; }
|
||||
public ThirdPartyAppHandler ThirdPartyAppHandler { get; }
|
||||
public ThirdPartyAppHandlerService ThirdPartyAppHandlerService { get; }
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
public ILog Logger { get; }
|
||||
|
||||
@ -137,7 +137,7 @@ namespace ASC.Web.Files.ThirdPartyApp
|
||||
SetupInfo setupInfo,
|
||||
TokenHelper tokenHelper,
|
||||
DocumentServiceConnector documentServiceConnector,
|
||||
ThirdPartyAppHandler thirdPartyAppHandler,
|
||||
ThirdPartyAppHandlerService thirdPartyAppHandlerService,
|
||||
IServiceProvider serviceProvider,
|
||||
TenantManager tenantManager,
|
||||
CoreBaseSettings coreBaseSettings,
|
||||
@ -165,7 +165,7 @@ namespace ASC.Web.Files.ThirdPartyApp
|
||||
SetupInfo = setupInfo;
|
||||
TokenHelper = tokenHelper;
|
||||
DocumentServiceConnector = documentServiceConnector;
|
||||
ThirdPartyAppHandler = thirdPartyAppHandler;
|
||||
ThirdPartyAppHandlerService = thirdPartyAppHandlerService;
|
||||
ServiceProvider = serviceProvider;
|
||||
Logger = option.CurrentValue;
|
||||
}
|
||||
@ -251,7 +251,7 @@ namespace ASC.Web.Files.ThirdPartyApp
|
||||
|
||||
Logger.Debug("BoxApp: get file stream url " + fileId);
|
||||
|
||||
var uriBuilder = new UriBuilder(BaseCommonLinkUtility.GetFullAbsolutePath(ThirdPartyAppHandler.HandlerPath));
|
||||
var uriBuilder = new UriBuilder(BaseCommonLinkUtility.GetFullAbsolutePath(ThirdPartyAppHandlerService.HandlerPath));
|
||||
if (uriBuilder.Uri.IsLoopback)
|
||||
{
|
||||
uriBuilder.Host = Dns.GetHostName();
|
||||
@ -476,7 +476,7 @@ namespace ASC.Web.Files.ThirdPartyApp
|
||||
|
||||
using (var stream = new ResponseStream(request.GetResponse()))
|
||||
{
|
||||
stream.StreamCopyTo(context.Response.Body);
|
||||
stream.CopyTo(context.Response.Body, StreamExtension.BufferSize);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -116,7 +116,7 @@ namespace ASC.Web.Files.ThirdPartyApp
|
||||
public GoogleLoginProvider GoogleLoginProvider { get; }
|
||||
public TokenHelper TokenHelper { get; }
|
||||
public DocumentServiceConnector DocumentServiceConnector { get; }
|
||||
public ThirdPartyAppHandler ThirdPartyAppHandler { get; }
|
||||
public ThirdPartyAppHandlerService ThirdPartyAppHandlerService { get; }
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
public GoogleDriveApp()
|
||||
@ -147,7 +147,7 @@ namespace ASC.Web.Files.ThirdPartyApp
|
||||
GoogleLoginProvider googleLoginProvider,
|
||||
TokenHelper tokenHelper,
|
||||
DocumentServiceConnector documentServiceConnector,
|
||||
ThirdPartyAppHandler thirdPartyAppHandler,
|
||||
ThirdPartyAppHandlerService thirdPartyAppHandlerService,
|
||||
IServiceProvider serviceProvider,
|
||||
TenantManager tenantManager,
|
||||
CoreBaseSettings coreBaseSettings,
|
||||
@ -180,7 +180,7 @@ namespace ASC.Web.Files.ThirdPartyApp
|
||||
GoogleLoginProvider = googleLoginProvider;
|
||||
TokenHelper = tokenHelper;
|
||||
DocumentServiceConnector = documentServiceConnector;
|
||||
ThirdPartyAppHandler = thirdPartyAppHandler;
|
||||
ThirdPartyAppHandlerService = thirdPartyAppHandlerService;
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
@ -257,7 +257,7 @@ namespace ASC.Web.Files.ThirdPartyApp
|
||||
{
|
||||
Logger.Debug("GoogleDriveApp: get file stream url " + fileId);
|
||||
|
||||
var uriBuilder = new UriBuilder(BaseCommonLinkUtility.GetFullAbsolutePath(ThirdPartyAppHandler.HandlerPath));
|
||||
var uriBuilder = new UriBuilder(BaseCommonLinkUtility.GetFullAbsolutePath(ThirdPartyAppHandlerService.HandlerPath));
|
||||
if (uriBuilder.Uri.IsLoopback)
|
||||
{
|
||||
uriBuilder.Host = Dns.GetHostName();
|
||||
@ -535,7 +535,7 @@ namespace ASC.Web.Files.ThirdPartyApp
|
||||
using (var response = request.GetResponse())
|
||||
using (var stream = new ResponseStream(response))
|
||||
{
|
||||
stream.StreamCopyTo(context.Response.Body);
|
||||
stream.CopyTo(context.Response.Body, StreamExtension.BufferSize);
|
||||
|
||||
var contentLength = jsonFile.Value<string>("size");
|
||||
Logger.Debug("GoogleDriveApp: get file stream contentLength - " + contentLength);
|
||||
|
@ -75,7 +75,11 @@ namespace ASC.Web.Files.Utils
|
||||
|
||||
public ChunkedUploadSession<T> GetSession<T>(string sessionId)
|
||||
{
|
||||
return (ChunkedUploadSession<T>)CommonSessionHolder(false).Get(sessionId);
|
||||
return (ChunkedUploadSession<T>)GetSession(sessionId);
|
||||
}
|
||||
public CommonChunkedUploadSession GetSession(string sessionId)
|
||||
{
|
||||
return CommonSessionHolder(false).Get(sessionId);
|
||||
}
|
||||
|
||||
public ChunkedUploadSession<T> CreateUploadSession<T>(File<T> file, long contentLength)
|
||||
|
@ -406,11 +406,11 @@ namespace ASC.Web.Files.Utils
|
||||
cacheFolderId = GlobalFolder.GetFolderShare<T>(DaoFactory);
|
||||
}
|
||||
|
||||
if (rootFolderId != null)
|
||||
if (!rootFolderId.Equals(default(T)))
|
||||
{
|
||||
parentFolders.Add(folderDao.GetFolder(rootFolderId));
|
||||
}
|
||||
if (cacheFolderId != null)
|
||||
if (!cacheFolderId.Equals(default(T)))
|
||||
{
|
||||
RemoveFromCahce(cacheFolderId, userID);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "asc-web-common",
|
||||
"version": "1.0.133",
|
||||
"version": "1.0.138",
|
||||
"description": "Ascensio System SIA common components and solutions library",
|
||||
"license": "AGPL-3.0",
|
||||
"files": [
|
||||
|
@ -3,6 +3,15 @@ import axios from "axios";
|
||||
import FilesFilter from "./filter";
|
||||
import * as fakeFiles from "./fake";
|
||||
|
||||
export function openEdit(fileId) {
|
||||
const options = {
|
||||
method: "get",
|
||||
url: `/files/file/${fileId}/openedit`
|
||||
};
|
||||
|
||||
return request(options);
|
||||
}
|
||||
|
||||
export function getFolderInfo(folderId) {
|
||||
const options = {
|
||||
method: "get",
|
||||
@ -305,3 +314,12 @@ export function setShareFiles(fileId, shareTo, access, notify, sharingMessage) {
|
||||
const data = { share, notify, sharingMessage };
|
||||
return request({ method: "put", url: `/files/file/${fileId}/share`, data });
|
||||
}
|
||||
|
||||
export function startUploadSession(folderId, fileName, fileSize, relativePath) {
|
||||
const data = { fileName, fileSize, relativePath };
|
||||
return request({ method: "post", url: `/files/${folderId}/upload/create_session.json`, data });
|
||||
}
|
||||
|
||||
export function uploadFile(url, data) {
|
||||
return axios.post(url, data);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Backdrop } from "asc-web-components";
|
||||
import { Backdrop, ProgressBar } from "asc-web-components";
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import i18n from './i18n';
|
||||
import { ARTICLE_PINNED_KEY } from "../../constants";
|
||||
@ -148,7 +148,7 @@ class PageLayoutComponent extends React.PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { showProgressBar, progressBarMaxValue, progressBarValue, progressBarDropDownContent, withBodyScroll, withBodyAutoFocus, progressBarLabel } = this.props;
|
||||
const { showProgressBar, progressBarValue, progressBarDropDownContent, withBodyScroll, withBodyAutoFocus, progressBarLabel } = this.props;
|
||||
return (
|
||||
<>
|
||||
{this.state.isBackdropAvailable && (
|
||||
@ -196,12 +196,8 @@ class PageLayoutComponent extends React.PureComponent {
|
||||
<SectionFilter className="section-header_filter">{this.state.sectionFilterContent}</SectionFilter>
|
||||
)}
|
||||
{this.state.isSectionBodyAvailable && (
|
||||
<>
|
||||
<SectionBody
|
||||
showProgressBar={showProgressBar}
|
||||
progressBarMaxValue={progressBarMaxValue}
|
||||
progressBarValue={progressBarValue}
|
||||
progressBarLabel={progressBarLabel}
|
||||
progressBarDropDownContent={progressBarDropDownContent}
|
||||
withScroll={withBodyScroll}
|
||||
autoFocus={withBodyAutoFocus}
|
||||
pinned={this.state.isArticlePinned}
|
||||
@ -214,6 +210,15 @@ class PageLayoutComponent extends React.PureComponent {
|
||||
<SectionPaging>{this.state.sectionPagingContent}</SectionPaging>
|
||||
)}
|
||||
</SectionBody>
|
||||
{showProgressBar && (
|
||||
<ProgressBar
|
||||
className="layout-progress-bar"
|
||||
label={progressBarLabel}
|
||||
percent={progressBarValue}
|
||||
dropDownContent={progressBarDropDownContent}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{this.state.isArticleAvailable && (
|
||||
@ -275,7 +280,6 @@ PageLayoutComponent.propTypes = {
|
||||
t: PropTypes.func,
|
||||
|
||||
showProgressBar: PropTypes.bool,
|
||||
progressBarMaxValue: PropTypes.number,
|
||||
progressBarValue: PropTypes.number,
|
||||
progressBarDropDownContent: PropTypes.any,
|
||||
progressBarLabel: PropTypes.string
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
import { utils, Scrollbar, ProgressBar } from "asc-web-components";
|
||||
import { utils, Scrollbar } from "asc-web-components";
|
||||
const { tablet } = utils.device;
|
||||
|
||||
const StyledSectionBody = styled.div`
|
||||
@ -28,11 +28,6 @@ const StyledSectionWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
|
||||
.layout-progress-bar {
|
||||
flex: 0 0 auto;
|
||||
margin-right: -16px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSpacer = styled.div`
|
||||
@ -49,7 +44,6 @@ class SectionBody extends React.Component {
|
||||
super(props);
|
||||
|
||||
this.focusRef = React.createRef();
|
||||
this.ref = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -60,17 +54,15 @@ class SectionBody extends React.Component {
|
||||
|
||||
render() {
|
||||
//console.log("PageLayout SectionBody render");
|
||||
const { children, withScroll, autoFocus, pinned, showProgressBar, progressBarMaxValue, progressBarValue, progressBarDropDownContent, progressBarLabel } = this.props;
|
||||
const { children, withScroll, autoFocus, pinned } = this.props;
|
||||
|
||||
const focusProps = autoFocus ? {
|
||||
ref: this.focusRef,
|
||||
tabIndex: 1
|
||||
} : {};
|
||||
|
||||
const width = this.ref.current && this.ref.current.offsetWidth;
|
||||
|
||||
return (
|
||||
<StyledSectionBody ref={this.ref} withScroll={withScroll}>
|
||||
<StyledSectionBody withScroll={withScroll}>
|
||||
{withScroll ? (
|
||||
<Scrollbar stype="mediumBlack">
|
||||
<StyledSectionWrapper>
|
||||
@ -78,16 +70,6 @@ class SectionBody extends React.Component {
|
||||
{children}
|
||||
<StyledSpacer pinned={pinned}/>
|
||||
</div>
|
||||
{showProgressBar && (
|
||||
<ProgressBar
|
||||
className="layout-progress-bar"
|
||||
label={progressBarLabel}
|
||||
value={progressBarValue}
|
||||
width={width}
|
||||
maxValue={progressBarMaxValue}
|
||||
dropDownContent={progressBarDropDownContent}
|
||||
/>
|
||||
)}
|
||||
</StyledSectionWrapper>
|
||||
</Scrollbar>
|
||||
) : (
|
||||
@ -107,11 +89,6 @@ SectionBody.propTypes = {
|
||||
withScroll: PropTypes.bool,
|
||||
autoFocus: PropTypes.bool,
|
||||
pinned: PropTypes.bool,
|
||||
showProgressBar: PropTypes.bool,
|
||||
progressBarMaxValue: PropTypes.number,
|
||||
progressBarValue: PropTypes.number,
|
||||
progressBarLabel: PropTypes.string,
|
||||
progressBarDropDownContent: PropTypes.any,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
|
@ -9,6 +9,11 @@ const StyledSection = styled.section`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.layout-progress-bar {
|
||||
position: sticky;
|
||||
margin-left: -16px;
|
||||
}
|
||||
|
||||
.section-header_filter {
|
||||
display: block;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
|
||||
import CustomScrollbarsVirtualList from '../scrollbar/custom-scrollbars-virtual-list'
|
||||
import DropDownItem from '../drop-down-item'
|
||||
import Backdrop from '../backdrop'
|
||||
import { FixedSizeList } from "react-window"
|
||||
import { VariableSizeList } from "react-window"
|
||||
import onClickOutside from "react-onclickoutside";
|
||||
|
||||
const StyledDropdown = styled.div`
|
||||
@ -43,12 +43,14 @@ const StyledDropdown = styled.div`
|
||||
// eslint-disable-next-line react/display-name, react/prop-types
|
||||
const Row = memo(({ data, index, style }) => {
|
||||
const option = data[index];
|
||||
const separator = option.props.isSeparator ? { width: `calc(100% - 32px)`, height: `1px` } : {}
|
||||
const newStyle = {...style, ...separator};
|
||||
|
||||
return (
|
||||
<DropDownItem
|
||||
// eslint-disable-next-line react/prop-types
|
||||
{...option.props}
|
||||
style={style} />
|
||||
style={newStyle} />
|
||||
);
|
||||
});
|
||||
|
||||
@ -115,12 +117,21 @@ class DropDown extends React.PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
getItemHeight = (item) => {
|
||||
const isTablet = window.innerWidth < 1024; //TODO: Make some better
|
||||
|
||||
if (item && item.props.isSeparator)
|
||||
return isTablet ? 16 : 12;
|
||||
|
||||
return isTablet ? 36 : 32;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { maxHeight, children } = this.props;
|
||||
const { directionX, directionY, width } = this.state;
|
||||
const isTablet = window.innerWidth < 1024; //TODO: Make some better
|
||||
const itemHeight = isTablet ? 36 : 32;
|
||||
const fullHeight = children && children.length * itemHeight;
|
||||
const rowHeights = React.Children.map(children, child => this.getItemHeight(child));
|
||||
const getItemSize = index => rowHeights[index];
|
||||
const fullHeight = children && rowHeights.reduce((a, b) => a + b, 0);
|
||||
const calculatedHeight = ((fullHeight > 0) && (fullHeight < maxHeight)) ? fullHeight : maxHeight;
|
||||
const dropDownMaxHeightProp = maxHeight ? { height: calculatedHeight + 'px' } : {};
|
||||
//console.log("DropDown render", this.props);
|
||||
@ -133,16 +144,16 @@ class DropDown extends React.PureComponent {
|
||||
{...dropDownMaxHeightProp}
|
||||
>
|
||||
{maxHeight
|
||||
? <FixedSizeList
|
||||
? <VariableSizeList
|
||||
height={calculatedHeight}
|
||||
width={width}
|
||||
itemSize={itemHeight}
|
||||
itemSize={getItemSize}
|
||||
itemCount={children.length}
|
||||
itemData={children}
|
||||
outerElementType={CustomScrollbarsVirtualList}
|
||||
>
|
||||
{Row}
|
||||
</FixedSizeList>
|
||||
</VariableSizeList>
|
||||
: children}
|
||||
</StyledDropdown>
|
||||
);
|
||||
|
@ -13,6 +13,7 @@ import { EmptyScreenContainer } from "asc-web-components";
|
||||
imageSrc="empty_screen_filter.png"
|
||||
imageAlt="Empty Screen Filter image"
|
||||
headerText="No results matching your search could be found"
|
||||
subheading="No files to be displayed in this section"
|
||||
descriptionText="No results matching your search could be found"
|
||||
buttons={<a href="/">Go to home</a>}
|
||||
/>
|
||||
@ -26,6 +27,7 @@ import { EmptyScreenContainer } from "asc-web-components";
|
||||
| `className` | `string` | - | - | - | Accepts class |
|
||||
| `descriptionText` | `string` | - | - | - | Description text |
|
||||
| `headerText` | `string` | - | - | - | Header text |
|
||||
| `subheadingText` | `string` | - | - | - | Subheading text |
|
||||
| `id` | `string` | - | - | - | Accepts id |
|
||||
| `imageAlt` | `string` | - | - | - | Alternative image text |
|
||||
| `imageSrc` | `string` | - | - | - | Image url source |
|
||||
|
@ -19,6 +19,10 @@ storiesOf("Components| EmptyScreenContainer", module)
|
||||
"headerText",
|
||||
"No results matching your search could be found"
|
||||
)}
|
||||
subheadingText={text(
|
||||
"subheaderText",
|
||||
"No files to be displayed in this section"
|
||||
)}
|
||||
descriptionText={text(
|
||||
"descriptionText",
|
||||
"No people matching your filter can be displayed in this section. Please select other filter options or clear filter to view all the people in this section."
|
||||
|
@ -10,6 +10,7 @@ const EmptyContentBody = styled.div`
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"img header"
|
||||
"img subheading"
|
||||
"img desc"
|
||||
"img button";
|
||||
min-width: 320px;
|
||||
@ -24,6 +25,11 @@ const EmptyContentBody = styled.div`
|
||||
grid-area: header;
|
||||
}
|
||||
|
||||
.ec-subheading {
|
||||
grid-area: subheading;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.ec-desc {
|
||||
grid-area: desc;
|
||||
padding-top: 5px;
|
||||
@ -44,6 +50,10 @@ const EmptyContentBody = styled.div`
|
||||
font-size: 3.5vw;
|
||||
}
|
||||
|
||||
.ec-subheading {
|
||||
font-size: 2.9vw;
|
||||
}
|
||||
|
||||
.ec-desc {
|
||||
font-size: 2.4vw;
|
||||
}
|
||||
@ -58,6 +68,10 @@ const EmptyContentBody = styled.div`
|
||||
font-size: 4.75vw;
|
||||
}
|
||||
|
||||
.ec-subheading {
|
||||
font-size: 4.15vw;
|
||||
}
|
||||
|
||||
.ec-desc {
|
||||
font-size: 3.7vw;
|
||||
}
|
||||
@ -73,7 +87,7 @@ const EmptyContentImage = styled.img.attrs(props => ({
|
||||
`;
|
||||
|
||||
const EmptyScreenContainer = props => {
|
||||
const { imageSrc, imageAlt, headerText, descriptionText, buttons } = props;
|
||||
const { imageSrc, imageAlt, headerText, subheadingText, descriptionText, buttons } = props;
|
||||
return (
|
||||
|
||||
<EmptyContentBody {...props}>
|
||||
@ -84,6 +98,10 @@ const EmptyScreenContainer = props => {
|
||||
<Text as="span" color="#333333" fontSize='24px' className="ec-header">{headerText}</Text>
|
||||
)}
|
||||
|
||||
{subheadingText && (
|
||||
<Text as="span" color="#737373" fontSize='18px' className="ec-subheading">{subheadingText}</Text>
|
||||
)}
|
||||
|
||||
{descriptionText && (
|
||||
<Text as="span" color="#737373" fontSize='14px' className="ec-desc">{descriptionText}</Text>
|
||||
)}
|
||||
@ -102,6 +120,7 @@ EmptyScreenContainer.propTypes = {
|
||||
imageSrc: PropTypes.string,
|
||||
imageAlt: PropTypes.string,
|
||||
headerText: PropTypes.string,
|
||||
subheadingText: PropTypes.string,
|
||||
descriptionText: PropTypes.string,
|
||||
buttons: PropTypes.any,
|
||||
className: PropTypes.string,
|
||||
|
@ -16,7 +16,6 @@ import { ProgressBar } from "asc-web-components";
|
||||
|
||||
| Props | Type | Required | Values | Default | Description |
|
||||
| :---------------: | :------: | :------: | :----: | :-----: | ---------------------- |
|
||||
| `value` | `number` | ✅ | - | - | Progress value. |
|
||||
| `percent` | `number` | ✅ | - | - | Progress value. |
|
||||
| `label` | `string` | - | - | - | Text in progress-bar. |
|
||||
| `maxValue` | `number` | - | - | 100 | Max value of progress. |
|
||||
| `dropDownContent` | `any` | - | - | - | Drop-down content. |
|
||||
|
@ -17,14 +17,14 @@ const StyledProgressBar = styled.div`
|
||||
}
|
||||
|
||||
.progress-bar_percent {
|
||||
width: ${props => props.percent}%;
|
||||
width: ${props => props.uploadedPercent}%;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
max-height: 22px;
|
||||
min-height: 22px;
|
||||
}
|
||||
.progress-bar_field {
|
||||
width: ${props => props.percent2}%;
|
||||
width: ${props => props.remainPercent}%;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
max-height: 22px;
|
||||
@ -32,6 +32,7 @@ const StyledProgressBar = styled.div`
|
||||
}
|
||||
|
||||
.progress-bar_percent {
|
||||
transition: width .6s ease;
|
||||
background: linear-gradient(90deg, #20d21f 75%, #b9d21f 100%);
|
||||
}
|
||||
|
||||
@ -56,8 +57,7 @@ const StyledProgressBar = styled.div`
|
||||
`;
|
||||
|
||||
const ProgressBar = props => {
|
||||
const { value, maxValue, label, dropDownContent, ...rest } = props;
|
||||
const percent = value > maxValue ? 100 : (value / maxValue) * 100;
|
||||
const { percent, label, dropDownContent, ...rest } = props;
|
||||
const ref = React.createRef();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@ -69,7 +69,7 @@ const ProgressBar = props => {
|
||||
|
||||
//console.log("ProgressBar render");
|
||||
return (
|
||||
<StyledProgressBar ref={ref} {...rest} percent={percent} percent2={100 - percent} >
|
||||
<StyledProgressBar ref={ref} {...rest} uploadedPercent={percent} remainPercent={100 - percent} >
|
||||
<Link
|
||||
className="progress-bar_full-text"
|
||||
color="#333"
|
||||
@ -101,14 +101,9 @@ const ProgressBar = props => {
|
||||
};
|
||||
|
||||
ProgressBar.propTypes = {
|
||||
value: PropTypes.number,
|
||||
maxValue: PropTypes.number,
|
||||
percent: PropTypes.number.isRequired,
|
||||
label: PropTypes.string,
|
||||
dropDownContent: PropTypes.any,
|
||||
};
|
||||
|
||||
ProgressBar.defaultProps = {
|
||||
maxValue: 100
|
||||
};
|
||||
|
||||
export default ProgressBar;
|
||||
|
@ -12,8 +12,7 @@ storiesOf("Components|ProgressBar", module)
|
||||
<ProgressBar
|
||||
style={{marginTop: 16}}
|
||||
label={text("label", "Uploading files: 20 of 100")}
|
||||
value={number("value", 20)}
|
||||
maxValue={number("maxValue", 100)}
|
||||
percent={number("value", 20)}
|
||||
dropDownContent={text("dropDownContent", "You content here")}
|
||||
/>
|
||||
));
|
||||
|
@ -38,9 +38,10 @@ namespace ASC.Web.Core.Files
|
||||
// to RFC 2231 (with clarifications from RFC 5987)
|
||||
if (fileName.Any(c => (int)c > 127))
|
||||
{
|
||||
//.netcore
|
||||
var str = withoutBase
|
||||
? "{0}; filename*=UTF-8''{2}"
|
||||
: "{0}; filename=\"{1}\"; filename*=UTF-8''{2}";
|
||||
: "{0}; filename=\"{2}\"; filename*=UTF-8''{2}";
|
||||
|
||||
return string.Format(str,
|
||||
inline ? "inline" : "attachment",
|
||||
|
@ -35,13 +35,12 @@ using System.Runtime.Serialization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using ASC.Common.Web;
|
||||
using ASC.Core;
|
||||
using JWT;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Formatting = Newtonsoft.Json.Formatting;
|
||||
|
||||
namespace ASC.Web.Core.Files
|
||||
{
|
||||
@ -145,12 +144,11 @@ namespace ASC.Web.Core.Files
|
||||
{
|
||||
{ "payload", body }
|
||||
};
|
||||
JsonWebToken.JsonSerializer = new JwtSerializer();
|
||||
var token = JsonWebToken.Encode(payload, signatureSecret, JwtHashAlgorithm.HS256);
|
||||
var token = JsonWebToken.Encode(payload, signatureSecret);
|
||||
//todo: remove old scheme
|
||||
request.Headers.Add(fileUtility.SignatureHeader, "Bearer " + token);
|
||||
|
||||
token = JsonWebToken.Encode(body, signatureSecret, JwtHashAlgorithm.HS256);
|
||||
token = JsonWebToken.Encode(body, signatureSecret);
|
||||
body.Token = token;
|
||||
}
|
||||
|
||||
@ -255,12 +253,12 @@ namespace ASC.Web.Core.Files
|
||||
{
|
||||
{ "payload", body }
|
||||
};
|
||||
JsonWebToken.JsonSerializer = new JwtSerializer();
|
||||
var token = JsonWebToken.Encode(payload, signatureSecret, JwtHashAlgorithm.HS256);
|
||||
|
||||
var token = JsonWebToken.Encode(payload, signatureSecret);
|
||||
//todo: remove old scheme
|
||||
request.Headers.Add(fileUtility.SignatureHeader, "Bearer " + token);
|
||||
|
||||
token = JsonWebToken.Encode(body, signatureSecret, JwtHashAlgorithm.HS256);
|
||||
token = JsonWebToken.Encode(body, signatureSecret);
|
||||
body.Token = token;
|
||||
}
|
||||
|
||||
@ -336,12 +334,11 @@ namespace ASC.Web.Core.Files
|
||||
{ "payload", body }
|
||||
};
|
||||
|
||||
JsonWebToken.JsonSerializer = new JwtSerializer();
|
||||
var token = JsonWebToken.Encode(payload, signatureSecret, JwtHashAlgorithm.HS256);
|
||||
var token = JsonWebToken.Encode(payload, signatureSecret);
|
||||
//todo: remove old scheme
|
||||
request.Headers.Add(fileUtility.SignatureHeader, "Bearer " + token);
|
||||
|
||||
token = JsonWebToken.Encode(body, signatureSecret, JwtHashAlgorithm.HS256);
|
||||
token = JsonWebToken.Encode(body, signatureSecret);
|
||||
body.Token = token;
|
||||
}
|
||||
|
||||
@ -625,43 +622,5 @@ namespace ASC.Web.Core.Files
|
||||
|
||||
return resultPercent;
|
||||
}
|
||||
|
||||
|
||||
public class JwtSerializer : IJsonSerializer
|
||||
{
|
||||
private class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
|
||||
{
|
||||
protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
|
||||
{
|
||||
var contract = base.CreateDictionaryContract(objectType);
|
||||
|
||||
contract.DictionaryKeyResolver = propertyName => propertyName;
|
||||
|
||||
return contract;
|
||||
}
|
||||
}
|
||||
|
||||
public string Serialize(object obj)
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCaseExceptDictionaryKeysResolver(),
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
};
|
||||
|
||||
return JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
|
||||
}
|
||||
|
||||
public T Deserialize<T>(string json)
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCaseExceptDictionaryKeysResolver(),
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
};
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(json, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -213,17 +213,17 @@ namespace ASC.Web.Core.Files
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> extsWebPreviewed { get => (Configuration["files.docservice.viewed-docs"] ?? "").Split(new char[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); }
|
||||
private List<string> extsWebEdited { get => (Configuration["files.docservice.edited-docs"] ?? "").Split(new char[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); }
|
||||
private List<string> extsWebReviewed { get => (Configuration["files.docservice.reviewed-docs"] ?? "").Split(new char[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); }
|
||||
private List<string> extsWebRestrictedEditing { get => (Configuration["files.docservice.formfilling-docs"] ?? "").Split(new char[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); }
|
||||
private List<string> extsWebCommented { get => (Configuration["files.docservice.commented-docs"] ?? "").Split(new char[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); }
|
||||
private List<string> extsMustConvert { get => (Configuration["files.docservice.convert-docs"] ?? "").Split(new char[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); }
|
||||
private List<string> extsIndexing { get => (Configuration["files.index.formats"] ?? "").Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); }
|
||||
private List<string> extsWebPreviewed { get => (Configuration.GetSection("files:docservice:viewed-docs").Get<string[]>() ?? new string[] { }).ToList(); }
|
||||
private List<string> extsWebEdited { get => (Configuration.GetSection("files:docservice:edited-docs").Get<string[]>() ?? new string[] { }).ToList(); }
|
||||
private List<string> extsWebReviewed { get => (Configuration.GetSection("files:docservice:reviewed-docs").Get<string[]>() ?? new string[] { }).ToList(); }
|
||||
private List<string> extsWebRestrictedEditing { get => (Configuration.GetSection("files:docservice:formfilling-docs").Get<string[]>() ?? new string[] { }).ToList(); }
|
||||
private List<string> extsWebCommented { get => (Configuration.GetSection("files:docservice:commented-docs").Get<string[]>() ?? new string[] { }).ToList(); }
|
||||
private List<string> extsMustConvert { get => (Configuration.GetSection("files:docservice:convert-docs").Get<string[]>() ?? new string[] { }).ToList(); }
|
||||
private List<string> extsIndexing { get => (Configuration.GetSection("files:index").Get<string[]>() ?? new string[] { }).ToList(); }
|
||||
|
||||
public List<string> ExtsImagePreviewed { get => (Configuration["files.viewed-images"] ?? "").Split(new char[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); }
|
||||
public List<string> ExtsImagePreviewed { get => (Configuration.GetSection("files:viewed-images").Get<string[]>() ?? new string[] { }).ToList(); }
|
||||
|
||||
public List<string> ExtsMediaPreviewed { get => (Configuration["files.viewed-media"] ?? "").Split(new char[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); }
|
||||
public List<string> ExtsMediaPreviewed { get => (Configuration.GetSection("files:viewed-media").Get<string[]>() ?? new string[] { }).ToList(); }
|
||||
|
||||
public List<string> ExtsWebPreviewed
|
||||
{
|
||||
@ -235,7 +235,7 @@ namespace ASC.Web.Core.Files
|
||||
get { return string.IsNullOrEmpty(FilesLinkUtility.DocServiceApiUrl) ? new List<string>() : extsWebEdited; }
|
||||
}
|
||||
|
||||
public List<string> ExtsWebEncrypt { get => (Configuration["files.docservice.encrypted-docs"] ?? "").Split(new char[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); }
|
||||
public List<string> ExtsWebEncrypt { get => (Configuration.GetSection("files:docservice:encrypted-docs").Get<string[]>() ?? new string[] { }).ToList(); }
|
||||
|
||||
public List<string> ExtsWebReviewed
|
||||
{
|
||||
@ -257,7 +257,7 @@ namespace ASC.Web.Core.Files
|
||||
get { return string.IsNullOrEmpty(FilesLinkUtility.DocServiceConverterUrl) ? new List<string>() : extsMustConvert; }
|
||||
}
|
||||
|
||||
public List<string> ExtsCoAuthoring { get => (Configuration["files.docservice.coauthor-docs"] ?? "").Split(new char[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); }
|
||||
public List<string> ExtsCoAuthoring { get => (Configuration.GetSection("files:docservice:coauthor-docs").Get<string[]>() ?? new string[] { }).ToList(); }
|
||||
public IConfiguration Configuration { get; }
|
||||
public FilesLinkUtility FilesLinkUtility { get; }
|
||||
|
||||
@ -330,9 +330,9 @@ namespace ASC.Web.Core.Files
|
||||
{
|
||||
get => new Dictionary<FileType, string>
|
||||
{
|
||||
{ FileType.Document, Configuration["files.docservice.internal-doc"] ?? ".docx" },
|
||||
{ FileType.Spreadsheet, Configuration["files.docservice.internal-xls"] ?? ".xlsx" },
|
||||
{ FileType.Presentation, Configuration["files.docservice.internal-ppt"] ?? ".pptx" }
|
||||
{ FileType.Document, Configuration["files:docservice:internal-doc"] ?? ".docx" },
|
||||
{ FileType.Spreadsheet, Configuration["files:docservice:internal-xls"] ?? ".xlsx" },
|
||||
{ FileType.Presentation, Configuration["files:docservice:internal-ppt"] ?? ".pptx" }
|
||||
};
|
||||
}
|
||||
|
||||
@ -350,7 +350,7 @@ namespace ASC.Web.Core.Files
|
||||
|
||||
private string GetSignatureSecret()
|
||||
{
|
||||
var result = Configuration["files.docservice.secret"] ?? "";
|
||||
var result = Configuration["files:docservice:secret:value"] ?? "";
|
||||
|
||||
var regex = new Regex(@"^\s+$");
|
||||
|
||||
@ -362,7 +362,7 @@ namespace ASC.Web.Core.Files
|
||||
|
||||
private string GetSignatureHeader()
|
||||
{
|
||||
var result = (Configuration["files.docservice.secret.header"] ?? "").Trim();
|
||||
var result = (Configuration["files:docservice:secret:header"] ?? "").Trim();
|
||||
if (string.IsNullOrEmpty(result))
|
||||
result = "Authorization";
|
||||
return result;
|
||||
|
@ -65,7 +65,7 @@ namespace ASC.Web.Core.Files
|
||||
CoreSettings = coreSettings;
|
||||
Configuration = configuration;
|
||||
InstanceCrypto = instanceCrypto;
|
||||
FilesUploaderURL = Configuration["files.uploader.url"] ?? "~";
|
||||
FilesUploaderURL = Configuration["files:uploader:url"] ?? "~";
|
||||
}
|
||||
|
||||
public string FilesBaseAbsolutePath
|
||||
@ -409,7 +409,7 @@ namespace ASC.Web.Core.Files
|
||||
}
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
value = Configuration["files.docservice.url." + (appSettingsKey ?? key)];
|
||||
value = Configuration["files:docservice:url:" + (appSettingsKey ?? key)];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
@ -389,6 +389,8 @@ namespace ASC.Web.Core.Sms
|
||||
set { }
|
||||
}
|
||||
|
||||
public VoipDao VoipDao { get; }
|
||||
|
||||
public override bool Enable()
|
||||
{
|
||||
return
|
||||
@ -425,6 +427,7 @@ namespace ASC.Web.Core.Sms
|
||||
}
|
||||
|
||||
public TwilioProvider(
|
||||
VoipDao voipDao,
|
||||
TenantManager tenantManager,
|
||||
CoreBaseSettings coreBaseSettings,
|
||||
CoreSettings coreSettings,
|
||||
@ -434,6 +437,7 @@ namespace ASC.Web.Core.Sms
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, options, name, order, props, additional)
|
||||
{
|
||||
VoipDao = voipDao;
|
||||
}
|
||||
|
||||
|
||||
@ -456,12 +460,11 @@ namespace ASC.Web.Core.Sms
|
||||
|
||||
var provider = new VoipService.Twilio.TwilioProvider(Key, Secret, authContext, tenantUtil, securityContext, baseCommonLinkUtility);
|
||||
|
||||
var dao = new CachedVoipDao(tenantManager.GetCurrentTenant().TenantId, dbOptions, authContext, tenantUtil, securityContext, baseCommonLinkUtility, ConsumerFactory, voipDaoCache);
|
||||
var numbers = dao.GetNumbers();
|
||||
var numbers = VoipDao.GetNumbers();
|
||||
foreach (var number in numbers)
|
||||
{
|
||||
provider.DisablePhone(number);
|
||||
dao.DeleteNumber(number.Id);
|
||||
VoipDao.DeleteNumber(number.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -473,6 +476,7 @@ namespace ASC.Web.Core.Sms
|
||||
}
|
||||
|
||||
public TwilioSaaSProvider(
|
||||
VoipDao voipDao,
|
||||
TenantManager tenantManager,
|
||||
CoreBaseSettings coreBaseSettings,
|
||||
CoreSettings coreSettings,
|
||||
@ -480,8 +484,21 @@ namespace ASC.Web.Core.Sms
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
IOptionsMonitor<ILog> options,
|
||||
string name, int order, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, options, name, order, null, additional)
|
||||
: base(voipDao, tenantManager, coreBaseSettings, coreSettings, configuration, cache, options, name, order, null, additional)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static class TwilioProviderExtention
|
||||
{
|
||||
public static DIHelper AddTwilioProviderService(this DIHelper services)
|
||||
{
|
||||
services.TryAddScoped<TwilioProvider>();
|
||||
services.TryAddScoped<TwilioSaaSProvider>();
|
||||
return services.AddVoipDaoService()
|
||||
.AddTenantManagerService()
|
||||
.AddCoreBaseSettingsService()
|
||||
.AddCoreSettingsService();
|
||||
}
|
||||
}
|
||||
}
|