Merge branch 'develop' into feature/colloborator

This commit is contained in:
Maksim Chegulov 2023-02-21 17:11:39 +03:00
commit 3401abb21a
135 changed files with 2491 additions and 1992 deletions

View File

@ -25,13 +25,21 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build 4testing
run: |
export BUILD_NUMBER="$(date "+%Y%m%d%H")"
cd ./build/install/docker
REPO="onlyoffice" \
REPO=${{ secrets.DOCKERHUB_USERNAME }} \
DOCKER_IMAGE_PREFIX="4testing-docspace" \
DOCKER_TAG="develop" \
DOCKER_TAG=$GITHUB_REF_NAME \
DOCKERFILE="Dockerfile.app" \
docker buildx bake -f build.yml \
--set *.args.GIT_BRANCH="develop" \
--set *.args.GIT_BRANCH=$GITHUB_REF_NAME \
--set *.platform=linux/amd64 \
--push
REPO=${{ secrets.DOCKERHUB_USERNAME }} \
DOCKER_IMAGE_PREFIX="4testing-docspace" \
DOCKER_TAG=$GITHUB_REF_NAME-$BUILD_NUMBER \
DOCKERFILE="Dockerfile.app" \
docker buildx bake -f build.yml \
--set *.args.GIT_BRANCH=$GITHUB_REF_NAME \
--push
shell: bash

View File

@ -71,8 +71,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Data.Backup", "common\s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Webhooks.Core", "common\ASC.Webhooks.Core\ASC.Webhooks.Core.csproj", "{760BFF3A-1A67-43A1-A94C-78D11A4BB1E6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Webhooks.Service", "common\services\ASC.Webhooks.Service\ASC.Webhooks.Service.csproj", "{DAE2912D-1465-4D60-B1D7-90EE835003E4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Data.Backup.BackgroundTasks", "common\services\ASC.Data.Backup.BackgroundTasks\ASC.Data.Backup.BackgroundTasks.csproj", "{C0C28A02-943C-4A38-B474-A2B49C6201ED}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.EventBus", "common\ASC.EventBus\ASC.EventBus.csproj", "{26540DA7-604B-474B-97BA-9CDC85A84B6D}"
@ -219,10 +217,6 @@ Global
{760BFF3A-1A67-43A1-A94C-78D11A4BB1E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{760BFF3A-1A67-43A1-A94C-78D11A4BB1E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{760BFF3A-1A67-43A1-A94C-78D11A4BB1E6}.Release|Any CPU.Build.0 = Release|Any CPU
{DAE2912D-1465-4D60-B1D7-90EE835003E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DAE2912D-1465-4D60-B1D7-90EE835003E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DAE2912D-1465-4D60-B1D7-90EE835003E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DAE2912D-1465-4D60-B1D7-90EE835003E4}.Release|Any CPU.Build.0 = Release|Any CPU
{C0C28A02-943C-4A38-B474-A2B49C6201ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C0C28A02-943C-4A38-B474-A2B49C6201ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C0C28A02-943C-4A38-B474-A2B49C6201ED}.Release|Any CPU.ActiveCfg = Release|Any CPU

View File

@ -20,6 +20,7 @@
"common\\ASC.MessagingSystem\\ASC.MessagingSystem.csproj",
"common\\ASC.Notify.Textile\\ASC.Notify.Textile.csproj",
"common\\ASC.Textile\\ASC.Textile.csproj",
"common\\ASC.Webhooks.Core\\ASC.Webhooks.Core.csproj",
"common\\services\\ASC.ApiCache\\ASC.ApiCache.csproj",
"common\\services\\ASC.ApiSystem\\ASC.ApiSystem.csproj",
"common\\services\\ASC.AuditTrail\\ASC.AuditTrail.csproj",

View File

@ -45,6 +45,7 @@ public abstract class BaseStartup
protected DIHelper DIHelper { get; }
protected bool LoadProducts { get; set; } = true;
protected bool LoadConsumers { get; } = true;
protected bool WebhooksEnabled { get; set; }
public BaseStartup(IConfiguration configuration, IHostEnvironment hostEnvironment)
{
@ -113,6 +114,7 @@ public abstract class BaseStartup
DIHelper.AddControllers();
DIHelper.TryAdd<CultureMiddleware>();
DIHelper.TryAdd<LoggerMiddleware>();
DIHelper.TryAdd<IpSecurityFilter>();
DIHelper.TryAdd<PaymentFilter>();
DIHelper.TryAdd<ProductSecurityFilter>();
@ -290,9 +292,11 @@ public abstract class BaseStartup
app.UseCultureMiddleware();
app.UseEndpoints(endpoints =>
app.UseLoggerMiddleware();
app.UseEndpoints(async endpoints =>
{
endpoints.MapCustom();
await endpoints.MapCustom(WebhooksEnabled, app.ApplicationServices);
endpoints.MapHealthChecks("/health", new HealthCheckOptions()
{

View File

@ -76,13 +76,59 @@ public class CustomEndpointDataSource : EndpointDataSource
public static class EndpointExtension
{
public static IEndpointRouteBuilder MapCustom(this IEndpointRouteBuilder endpoints)
private static readonly IReadOnlyList<string> _methodList = new List<string>
{
"POST",
"PUT",
"DELETE"
};
public static async Task<IEndpointRouteBuilder> MapCustom(this IEndpointRouteBuilder endpoints, bool webhooksEnabled = false, IServiceProvider serviceProvider = null)
{
endpoints.MapControllers();
if (webhooksEnabled && serviceProvider != null)
{
await endpoints.RegisterWebhooks(serviceProvider);
}
var sources = endpoints.DataSources.First();
endpoints.DataSources.Clear();
endpoints.DataSources.Add(new CustomEndpointDataSource(sources));
return endpoints;
}
private static async Task<IEndpointRouteBuilder> RegisterWebhooks(this IEndpointRouteBuilder endpoints, IServiceProvider serviceProvider)
{
var toRegister = endpoints.DataSources.First().Endpoints
.Cast<RouteEndpoint>()
.SelectMany(r =>
{
var result = new List<Webhook>();
var httpMethodMetadata = r.Metadata.OfType<HttpMethodMetadata>().FirstOrDefault();
var disabled = r.Metadata.OfType<WebhookDisableAttribute>().FirstOrDefault();
if (disabled == null)
{
foreach (var httpMethod in httpMethodMetadata.HttpMethods)
{
result.Add(new Webhook { Method = httpMethod, Route = r.RoutePattern.RawText.ToLower() });
}
}
return result;
})
.Where(r => _methodList.Contains(r.Method))
.DistinctBy(r => $"{r.Method}|{r.Route}")
.ToList();
using var scope = serviceProvider.CreateScope();
var dbWorker = scope.ServiceProvider.GetService<DbWorker>();
if (dbWorker != null)
{
await dbWorker.Register(toRegister);
}
return endpoints;
}
}

View File

@ -0,0 +1,32 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Api.Core.Core;
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class WebhookDisableAttribute : Attribute
{
}

View File

@ -0,0 +1,78 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using Amazon.Runtime.Internal.Transform;
namespace ASC.Api.Core.Middleware;
public class LoggerMiddleware
{
private readonly RequestDelegate _next;
public LoggerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context,
TenantManager tenantManager,
CoreSettings coreSettings,
ILogger<LoggerMiddleware> logger)
{
var tenant = tenantManager.GetCurrentTenant(false, context);
if (tenant == null)
{
await _next.Invoke(context);
return;
}
var state = new Dictionary<string, object>()
{
new KeyValuePair<string, object>("tenantId", tenant.Id),
new KeyValuePair<string, object>("tenantAlias", tenant.GetTenantDomain(coreSettings, false))
};
if (tenant.MappedDomain != null)
{
state.Add("tenantMappedDomain", tenant.MappedDomain);
}
using (logger.BeginScope(state.ToArray()))
{
await _next.Invoke(context);
}
}
}
public static class LoggerMiddlewareExtensions
{
public static IApplicationBuilder UseLoggerMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<LoggerMiddleware>();
}
}

View File

@ -33,31 +33,33 @@ public class WebhooksGlobalFilterAttribute : ResultFilterAttribute, IDisposable
private Stream _bodyStream;
private readonly IWebhookPublisher _webhookPublisher;
private readonly ILogger<WebhooksGlobalFilterAttribute> _logger;
private static readonly List<string> _methodList = new List<string> { "POST", "UPDATE", "DELETE" };
private readonly SettingsManager _settingsManager;
private readonly DbWorker _dbWorker;
public WebhooksGlobalFilterAttribute(IWebhookPublisher webhookPublisher, ILogger<WebhooksGlobalFilterAttribute> logger)
public WebhooksGlobalFilterAttribute(
IWebhookPublisher webhookPublisher,
ILogger<WebhooksGlobalFilterAttribute> logger,
SettingsManager settingsManager,
DbWorker dbWorker)
{
_stream = new MemoryStream();
_webhookPublisher = webhookPublisher;
_logger = logger;
_settingsManager = settingsManager;
_dbWorker = dbWorker;
}
public override void OnResultExecuting(ResultExecutingContext context)
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
if (!Skip(context.HttpContext))
if (!await Skip(context.HttpContext))
{
_bodyStream = context.HttpContext.Response.Body;
context.HttpContext.Response.Body = _stream;
}
base.OnResultExecuting(context);
}
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
await base.OnResultExecutionAsync(context, next);
if (context.Cancel || Skip(context.HttpContext))
if (context.Cancel || await Skip(context.HttpContext))
{
return;
}
@ -74,7 +76,9 @@ public class WebhooksGlobalFilterAttribute : ResultFilterAttribute, IDisposable
var resultContent = Encoding.UTF8.GetString(_stream.ToArray());
await _webhookPublisher.PublishAsync(method, routePattern, resultContent);
var webhook = await _dbWorker.GetWebhookAsync(method, routePattern);
await _webhookPublisher.PublishAsync(webhook.Id, resultContent);
}
catch (Exception e)
{
@ -100,16 +104,17 @@ public class WebhooksGlobalFilterAttribute : ResultFilterAttribute, IDisposable
return (method, routePattern);
}
private bool Skip(HttpContext context)
private async Task<bool> Skip(HttpContext context)
{
var (method, routePattern) = GetData(context);
if (!_methodList.Contains(method))
if (routePattern == null)
{
return true;
}
if (routePattern == null)
var webhook = await _dbWorker.GetWebhookAsync(method, routePattern);
if (webhook == null || _settingsManager.Load<WebHooksSettings>().Ids.Contains(webhook.Id))
{
return true;
}

View File

@ -114,7 +114,7 @@ public class EmployeeDtoHelper
if (_httpContext.Check("avatarSmall"))
{
result.AvatarSmall = await _userPhotoManager.GetSmallPhotoURL(userInfo.Id) + $"?_={cacheKey}";
result.AvatarSmall = await _userPhotoManager.GetSmallPhotoURL(userInfo.Id) + $"?hash={cacheKey}";
}
if (result.Id != Guid.Empty)

View File

@ -37,4 +37,19 @@
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\WebHookResource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>WebHookResource.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\WebHookResource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>WebHookResource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using AutoMapper;
namespace ASC.Webhooks.Core;
[Scope]
@ -32,6 +34,7 @@ public class DbWorker
private readonly IDbContextFactory<WebhooksDbContext> _dbContextFactory;
private readonly TenantManager _tenantManager;
private readonly AuthContext _authContext;
private readonly IMapper _mapper;
private int Tenant
{
@ -41,18 +44,32 @@ public class DbWorker
}
}
public DbWorker(IDbContextFactory<WebhooksDbContext> dbContextFactory, TenantManager tenantManager, AuthContext authContext)
public DbWorker(
IDbContextFactory<WebhooksDbContext> dbContextFactory,
TenantManager tenantManager,
AuthContext authContext,
IMapper mapper)
{
_dbContextFactory = dbContextFactory;
_tenantManager = tenantManager;
_authContext = authContext;
_mapper = mapper;
}
public async Task<WebhooksConfig> AddWebhookConfig(string name, string uri, string secretKey)
public async Task<WebhooksConfig> AddWebhookConfig(string uri, string secretKey)
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var toAdd = new WebhooksConfig { TenantId = Tenant, Uri = uri, SecretKey = secretKey, Name = name };
var objForCreate = await webhooksDbContext.WebhooksConfigs
.Where(it => it.TenantId == Tenant && it.Uri == uri)
.FirstOrDefaultAsync();
if (objForCreate != null)
{
return objForCreate;
}
var toAdd = new WebhooksConfig { TenantId = Tenant, Uri = uri, SecretKey = secretKey };
toAdd = await webhooksDbContext.AddOrUpdateAsync(r => r.WebhooksConfigs, toAdd);
await webhooksDbContext.SaveChangesAsync();
@ -83,7 +100,7 @@ public class DbWorker
.AsAsyncEnumerable();
}
public async Task<WebhooksConfig> UpdateWebhookConfig(int id, string name, string uri, string key, bool? enabled)
public async Task<WebhooksConfig> UpdateWebhookConfig(int id, string uri, string key, bool? enabled)
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
@ -98,11 +115,6 @@ public class DbWorker
updateObj.Uri = uri;
}
if (!string.IsNullOrEmpty(name))
{
updateObj.Name = name;
}
if (!string.IsNullOrEmpty(key))
{
updateObj.SecretKey = key;
@ -130,13 +142,16 @@ public class DbWorker
.Where(it => it.TenantId == tenant && it.Id == id)
.FirstOrDefaultAsync();
if (removeObj != null)
{
webhooksDbContext.WebhooksConfigs.Remove(removeObj);
await webhooksDbContext.SaveChangesAsync();
}
return removeObj;
}
public IAsyncEnumerable<WebhooksLog> ReadJournal(int startIndex, int limit, DateTime? delivery, string hookname, string route)
public IAsyncEnumerable<WebhooksLog> ReadJournal(int startIndex, int limit, DateTime? delivery, string hookUri, int hookId)
{
var webhooksDbContext = _dbContextFactory.CreateDbContext();
@ -150,14 +165,14 @@ public class DbWorker
q = q.Where(r => r.Delivery == date);
}
if (!string.IsNullOrEmpty(hookname))
if (!string.IsNullOrEmpty(hookUri))
{
q = q.Where(r => r.Config.Name == hookname);
q = q.Where(r => r.Config.Uri == hookUri);
}
if (!string.IsNullOrEmpty(route))
if (hookId != 0)
{
q = q.Where(r => r.Route == route);
q = q.Where(r => r.WebhookId == hookId);
}
if (startIndex != 0)
@ -212,4 +227,53 @@ public class DbWorker
return webhook;
}
public async Task Register(List<Webhook> webhooks)
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var dbWebhooks = await webhooksDbContext.Webhooks.ToListAsync();
foreach (var webhook in webhooks)
{
if (!dbWebhooks.Any(r => r.Route == webhook.Route && r.Method == webhook.Method))
{
try
{
await webhooksDbContext.Webhooks.AddAsync(_mapper.Map<DbWebhook>(webhook));
await webhooksDbContext.SaveChangesAsync();
}
catch (Exception)
{
}
}
}
}
public async Task<List<Webhook>> GetWebhooksAsync()
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var webHooks = await webhooksDbContext.Webhooks.AsNoTracking().ToListAsync();
return _mapper.Map<List<DbWebhook>, List<Webhook>>(webHooks);
}
public async Task<Webhook> GetWebhookAsync(int id)
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var webHook = await webhooksDbContext.Webhooks.Where(r => r.Id == id).AsNoTracking().FirstOrDefaultAsync();
return _mapper.Map<DbWebhook, Webhook>(webHook);
}
public async Task<Webhook> GetWebhookAsync(string method, string routePattern)
{
using var webhooksDbContext = _dbContextFactory.CreateDbContext();
var webHook = await webhooksDbContext.Webhooks
.Where(r => r.Method == method && r.Route == routePattern)
.AsNoTracking()
.FirstOrDefaultAsync();
return _mapper.Map<DbWebhook, Webhook>(webHook);
}
}

View File

@ -30,6 +30,7 @@ public class WebhooksDbContext : DbContext
{
public DbSet<WebhooksConfig> WebhooksConfigs { get; set; }
public DbSet<WebhooksLog> WebhooksLogs { get; set; }
public DbSet<DbWebhook> Webhooks { get; set; }
public WebhooksDbContext(DbContextOptions<WebhooksDbContext> options) : base(options) { }
@ -37,6 +38,7 @@ public class WebhooksDbContext : DbContext
{
ModelBuilderWrapper
.From(modelBuilder, Database)
.AddDbWebhooks()
.AddWebhooksConfig()
.AddWebhooksLog();
}

View File

@ -0,0 +1,95 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Webhooks.Core.EF.Model;
public class DbWebhook : IMapFrom<Webhook>
{
public int Id { get; set; }
public string Route { get; set; }
public string Method { get; set; }
}
public static class DbWebhookExtension
{
public static ModelBuilderWrapper AddDbWebhooks(this ModelBuilderWrapper modelBuilder)
{
return modelBuilder
.Add(MySqlAddDbWebhook, Provider.MySql)
.Add(PgSqlAddDbWebhook, Provider.PostgreSql);
}
private static void MySqlAddDbWebhook(this ModelBuilder modelBuilder)
{
modelBuilder.Entity<DbWebhook>(entity =>
{
entity.HasKey(e => new { e.Id })
.HasName("PRIMARY");
entity.ToTable("webhooks")
.HasCharSet("utf8");
entity.Property(e => e.Id)
.HasColumnType("int")
.HasColumnName("id");
entity.Property(e => e.Route)
.HasMaxLength(200)
.HasColumnName("route")
.HasDefaultValueSql("''");
entity.Property(e => e.Method)
.HasMaxLength(10)
.HasColumnName("method")
.HasDefaultValueSql("''");
});
}
private static void PgSqlAddDbWebhook(this ModelBuilder modelBuilder)
{
modelBuilder.Entity<DbWebhook>(entity =>
{
entity.HasKey(e => new { e.Id })
.HasName("PRIMARY");
entity.ToTable("webhooks");
entity.Property(e => e.Id)
.HasColumnType("int")
.HasColumnName("id");
entity.Property(e => e.Route)
.HasMaxLength(200)
.HasColumnName("route")
.HasDefaultValueSql("''");
entity.Property(e => e.Method)
.HasMaxLength(10)
.HasColumnName("method")
.HasDefaultValueSql("''");
});
}
}

View File

@ -29,7 +29,6 @@ namespace ASC.Webhooks.Core.EF.Model;
public class WebhooksConfig : BaseEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string SecretKey { get; set; }
public int TenantId { get; set; }
public string Uri { get; set; }
@ -81,11 +80,6 @@ public static class WebhooksConfigExtension
.HasColumnName("secret_key")
.HasDefaultValueSql("''");
entity.Property(e => e.Name)
.HasMaxLength(50)
.HasColumnName("name")
.IsRequired();
entity.Property(e => e.Enabled)
.HasColumnName("enabled")
.HasDefaultValueSql("'1'")
@ -123,11 +117,6 @@ public static class WebhooksConfigExtension
.HasColumnName("secret_key")
.HasDefaultValueSql("''");
entity.Property(e => e.Name)
.HasMaxLength(50)
.HasColumnName("name")
.IsRequired();
entity.Property(e => e.Enabled)
.HasColumnName("enabled")
.HasDefaultValueSql("true");

View File

@ -28,11 +28,10 @@ namespace ASC.Webhooks.Core.EF.Model;
public class WebhooksLog
{
public int Id { get; set; }
public int ConfigId { get; set; }
public DateTime CreationTime { get; set; }
public int Id { get; set; }
public string Method { get; set; }
public string Route { get; set; }
public int WebhookId { get; set; }
public string RequestHeaders { get; set; }
public string RequestPayload { get; set; }
public string ResponseHeaders { get; set; }
@ -111,15 +110,10 @@ public static class WebhooksPayloadExtension
.HasColumnName("response_headers")
.HasColumnType("json");
entity.Property(e => e.Method)
.HasColumnType("varchar")
.HasColumnName("method")
.HasMaxLength(100);
entity.Property(e => e.Route)
.HasColumnType("varchar")
.HasColumnName("route")
.HasMaxLength(100);
entity.Property(e => e.WebhookId)
.HasColumnType("int")
.HasColumnName("webhook_id")
.IsRequired();
entity.Property(e => e.CreationTime)
.HasColumnType("datetime")
@ -180,15 +174,10 @@ public static class WebhooksPayloadExtension
.HasColumnName("response_headers")
.HasColumnType("json");
entity.Property(e => e.Method)
.HasColumnType("varchar")
.HasColumnName("method")
.HasMaxLength(100);
entity.Property(e => e.Route)
.HasColumnType("varchar")
.HasColumnName("route")
.HasMaxLength(100);
entity.Property(e => e.WebhookId)
.HasColumnType("int")
.HasColumnName("webhook_id")
.IsRequired();
entity.Property(e => e.CreationTime)
.HasColumnType("datetime")

View File

@ -24,13 +24,18 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
global using System.Text.Json.Serialization;
global using ASC.Common;
global using ASC.Common.Caching;
global using ASC.Common.Mapping;
global using ASC.Core;
global using ASC.Core.Common.EF;
global using ASC.Core.Common.EF.Model;
global using ASC.Core.Common.Settings;
global using ASC.Web.Webhooks;
global using ASC.Webhooks.Core.EF.Context;
global using ASC.Webhooks.Core.EF.Model;
global using ASC.Webhooks.Core.Resources;
global using Microsoft.EntityFrameworkCore;

View File

@ -29,6 +29,6 @@ namespace ASC.Webhooks.Core;
[Scope]
public interface IWebhookPublisher
{
public Task PublishAsync(string method, string route, string requestPayload);
public Task<WebhooksLog> PublishAsync(string method, string route, string requestPayload, int configId);
public Task PublishAsync(int webhookId, string requestPayload);
public Task<WebhooksLog> PublishAsync(int webhookId, string requestPayload, int configId);
}

View File

@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ASC.Webhooks.Core.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class WebHookResource {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal WebHookResource() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ASC.Webhooks.Core.Resources.WebHookResource", typeof(WebHookResource).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Start Edit.
/// </summary>
internal static string POST_api_2_0_files_file__fileId__startedit {
get {
return ResourceManager.GetString("POST|api/2.0/files/file/{fileId}/startedit", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Start Edit Description.
/// </summary>
internal static string POST_api_2_0_files_file__fileId__startedit_Description {
get {
return ResourceManager.GetString("POST|api/2.0/files/file/{fileId}/startedit_Description", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="POST|api/2.0/files/file/{fileId}/startedit" xml:space="preserve">
<value>Start Edit</value>
</data>
<data name="POST|api/2.0/files/file/{fileId}/startedit_Description" xml:space="preserve">
<value>Start Edit Description</value>
</data>
</root>

View File

@ -24,28 +24,23 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
global using System.Collections.Concurrent;
global using System.Security.Cryptography;
global using System.Text;
global using System.Text.Json;
global using ASC.Api.Core;
global using ASC.Api.Core.Extensions;
global using ASC.Common;
global using ASC.Common.Caching;
global using ASC.Common.Log;
global using ASC.Common.Utils;
global using ASC.Web.Webhooks;
global using ASC.Webhooks.Core;
global using ASC.Webhooks.Service;
global using ASC.Webhooks.Service.Log;
global using ASC.Webhooks.Service.Services;
namespace ASC.Webhooks.Core;
[Serializable]
public class WebHooksSettings : ISettings<WebHooksSettings>
{
public bool EnableSSLVerification { get; set; }
public List<int> Ids { get; set; }
[JsonIgnore]
public Guid ID => new Guid("6EFA0EAB-D033-4720-BDB3-DEB057EBC140");
public WebHooksSettings GetDefault() => new WebHooksSettings()
{
EnableSSLVerification = true,
Ids = new List<int> { }
};
}
global using Microsoft.AspNetCore.Builder;
global using Microsoft.Extensions.Hosting.WindowsServices;
global using Microsoft.Extensions.Logging;
global using Polly;
global using Polly.Extensions.Http;
global using ASC.Webhooks;
global using ASC.Webhooks.Extension;

View File

@ -0,0 +1,39 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Webhooks.Core;
public class Webhook : IMapFrom<DbWebhook>
{
public int Id { get; set; }
public string Route { get; set; }
public string Method { get; set; }
public bool Disable { get; set; }
public string Name { get => WebHookResource.ResourceManager.GetString(Endpoint) ?? ""; }
public string Description { get => WebHookResource.ResourceManager.GetString($"{Endpoint}_Description") ?? ""; }
private string Endpoint { get => $"{Method}|{Route}"; }
}

View File

@ -40,7 +40,7 @@ public class WebhookPublisher : IWebhookPublisher
_webhookNotify = webhookNotify;
}
public async Task PublishAsync(string method, string route, string requestPayload)
public async Task PublishAsync(int webhookId, string requestPayload)
{
if (string.IsNullOrEmpty(requestPayload))
{
@ -51,11 +51,11 @@ public class WebhookPublisher : IWebhookPublisher
await foreach (var config in webhookConfigs.Where(r => r.Enabled))
{
_ = await PublishAsync(method, route, requestPayload, config.Id);
_ = await PublishAsync(webhookId, requestPayload, config.Id);
}
}
public async Task<WebhooksLog> PublishAsync(string method, string route, string requestPayload, int configId)
public async Task<WebhooksLog> PublishAsync(int webhookId, string requestPayload, int configId)
{
if (string.IsNullOrEmpty(requestPayload))
{
@ -64,8 +64,7 @@ public class WebhookPublisher : IWebhookPublisher
var webhooksLog = new WebhooksLog
{
Method = method,
Route = route,
WebhookId = webhookId,
CreationTime = DateTime.UtcNow,
RequestPayload = requestPayload,
ConfigId = configId

View File

@ -30,9 +30,13 @@ global using ASC.Common;
global using ASC.Common.DependencyInjection;
global using ASC.Core.Notify;
global using ASC.Notify;
global using ASC.Notify.Extension;
global using ASC.Studio.Notify;
global using ASC.Web.Studio.Core.Notify;
global using Autofac;
global using Microsoft.AspNetCore.Builder;
global using Microsoft.Extensions.Hosting.WindowsServices;
global using NLog;

View File

@ -24,11 +24,6 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using ASC.Notify.Extension;
using ASC.Studio.Notify;
using NLog;
var options = new WebApplicationOptions
{
Args = args,
@ -42,7 +37,8 @@ builder.Configuration.AddDefaultConfiguration(builder.Environment)
.AddEnvironmentVariables()
.AddCommandLine(args);
var logger = LogManager.Setup()
var logger = LogManager
.Setup()
.SetupExtensions(s =>
{
s.RegisterLayoutRenderer("application-context", (logevent) => AppName);

View File

@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ASC.Api.Core\ASC.Api.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -1,87 +0,0 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using NLog;
var options = new WebApplicationOptions
{
Args = args,
ContentRootPath = WindowsServiceHelpers.IsWindowsService() ? AppContext.BaseDirectory : default
};
var builder = WebApplication.CreateBuilder(options);
builder.Configuration.AddDefaultConfiguration(builder.Environment)
.AddWebhookConfiguration()
.AddEnvironmentVariables()
.AddCommandLine(args);
var logger = LogManager.Setup()
.SetupExtensions(s =>
{
s.RegisterLayoutRenderer("application-context", (logevent) => AppName);
})
.LoadConfiguration(builder.Configuration, builder.Environment)
.GetLogger(typeof(Startup).Namespace);
try
{
logger.Info("Configuring web host ({applicationContext})...", AppName);
builder.Host.ConfigureDefault();
builder.WebHost.ConfigureDefaultKestrel();
var startup = new Startup(builder.Configuration, builder.Environment);
startup.ConfigureServices(builder.Services);
var app = builder.Build();
startup.Configure(app);
logger.Info("Starting web host ({applicationContext})...", AppName);
await app.RunWithTasksAsync();
}
catch (Exception ex)
{
if (logger != null)
{
logger.Error(ex, "Program terminated unexpectedly ({applicationContext})!", AppName);
}
throw;
}
finally
{
// Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
LogManager.Shutdown();
}
public partial class Program
{
public static string Namespace = typeof(Startup).Namespace;
public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.') + 1).Replace(".", "");
}

View File

@ -1,29 +0,0 @@
{
"profiles": {
"Kestrel WebServer": {
"commandName": "Project",
"launchBrowser": false,
"environmentVariables": {
"$STORAGE_ROOT": "../../../Data",
"log__name": "webhooks",
"log__dir": "../../../Logs",
"core__products__folder": "../../../products",
"ASPNETCORE_URLS": "http://localhost:5031",
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"WSL 2 : Ubuntu 20.04": {
"commandName": "WSL2",
"launchBrowser": false,
"environmentVariables": {
"$STORAGE_ROOT": "../../../Data",
"log__name": "webhooks",
"log__dir": "../../../Logs",
"core__products__folder": "../../../products",
"ASPNETCORE_URLS": "http://localhost:5031",
"ASPNETCORE_ENVIRONMENT": "Development"
},
"distributionName": "Ubuntu-20.04"
}
}
}

View File

@ -1,3 +0,0 @@
{
"pathToConf": "..\\..\\..\\config"
}

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
throwConfigExceptions="false"
throwConfigExceptions="true"
autoReload="true">
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
@ -10,7 +10,6 @@
<variable name="dir" value="..\Logs\"/>
<variable name="name" value="web"/>
<conversionPattern value=""/>
<targets async="true">
<default-target-parameters type="File" maxArchiveDays="30" archiveNumbering="DateAndSequence" archiveEvery="Day" enableArchiveFileCompression="true" archiveAboveSize="52428800" archiveDateFormat="MM-dd" layout="${date:format=yyyy-MM-dd HH\:mm\:ss,fff} ${level:uppercase=true} [${threadid}] ${logger} - ${message} ${exception:format=ToString}"/>
@ -18,7 +17,7 @@
<target name="sql" type="File" fileName="${var:dir}${var:name}.sql.log" layout="${date:universalTime=true:format=yyyy-MM-dd HH\:mm\:ss,fff}|${threadid}|${event-properties:item=elapsed}|${replace:inner=${event-properties:item=commandText}:searchFor=\\r\\n|\\s:replaceWith= :regex=true}|${event-properties:item=parameters}"/>
<target name="ownFile-web" type="File" fileName="${var:dir}${var:name}.asp.log" layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />
<target type="AWSTarget" name="aws" logGroup="/asc/docspace/cluster/cluster_name/general" region="us-east-1" LogStreamNamePrefix="${hostname} - ${application-context}">
<layout xsi:type="JsonLayout" includeAllProperties="true">
<layout xsi:type="JsonLayout" includeEventProperties="true" includeScopeProperties="true" maxRecursionLimit="2">
<attribute name="date" layout="${date:format=yyyy-MM-dd HH\:mm\:ss,fff}" />
<attribute name="level" layout="${level:upperCase=true}"/>
<attribute name="instanceId" layout="${hostname}"/>
@ -30,7 +29,7 @@
</layout>
</target>
<target type="AWSTarget" name="aws_sql" logGroup="/asc/docspace/cluster/cluster_name/sql" region="us-east-1" LogStreamNamePrefix="${hostname} - ${application-context}">
<layout xsi:type="JsonLayout" includeAllProperties="true">
<layout xsi:type="JsonLayout" includeEventProperties="true" includeScopeProperties="true">
<attribute name="date" layout="${date:universalTime=true:format=yyyy-MM-dd HH\:mm\:ss,fff}" />
<attribute name="instanceId" layout="${hostname}"/>
<attribute name="applicationContext" layout="${application-context}"/>

View File

@ -21,6 +21,33 @@ namespace ASC.Migrations.MySql.Migrations
.HasAnnotation("ProductVersion", "6.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.DbWebhook", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Route")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("varchar(200)")
.HasColumnName("route");
b.Property<string>("Method")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("varchar(10)")
.HasColumnName("method");
b.HasKey("Id")
.HasName("PRIMARY");
b.ToTable("webhooks", (string)null);
b.HasAnnotation("MySql:CharSet", "utf8");
});
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b =>
{
b.Property<int>("Id")
@ -88,11 +115,6 @@ namespace ASC.Migrations.MySql.Migrations
.HasColumnType("datetime")
.HasColumnName("delivery");
b.Property<string>("Method")
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnName("method");
b.Property<string>("RequestHeaders")
.HasColumnType("json")
.HasColumnName("request_headers");
@ -114,10 +136,9 @@ namespace ASC.Migrations.MySql.Migrations
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<string>("Route")
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnName("route");
b.Property<int>("ConfigId")
.HasColumnType("int")
.HasColumnName("config_id");
b.Property<int>("Status")
.HasColumnType("int")

View File

@ -1,11 +1,36 @@
using System;
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ASC.Migrations.MySql.Migrations
{
namespace ASC.Migrations.MySql.Migrations;
public partial class WebhooksDbContextMigrate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
@ -13,6 +38,23 @@ namespace ASC.Migrations.MySql.Migrations
migrationBuilder.AlterDatabase()
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "webhooks",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
route = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false, defaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8"),
method = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: false, defaultValueSql: "''")
.Annotation("MySql:CharSet", "utf8")
},
constraints: table =>
{
table.PrimaryKey("PRIMARY", x => x.id);
})
.Annotation("MySql:CharSet", "utf8");
migrationBuilder.CreateTable(
name: "webhooks_config",
columns: table => new
@ -42,10 +84,7 @@ namespace ASC.Migrations.MySql.Migrations
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
config_id = table.Column<int>(type: "int", nullable: false),
creation_time = table.Column<DateTime>(type: "datetime", nullable: false),
method = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
.Annotation("MySql:CharSet", "utf8"),
route = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
.Annotation("MySql:CharSet", "utf8"),
webhook_id = table.Column<int>(type: "int", nullable: false),
request_headers = table.Column<string>(type: "json", nullable: true)
.Annotation("MySql:CharSet", "utf8"),
request_payload = table.Column<string>(type: "text", nullable: false, collation: "utf8_general_ci")
@ -90,6 +129,9 @@ namespace ASC.Migrations.MySql.Migrations
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "webhooks");
migrationBuilder.DropTable(
name: "webhooks_logs");
@ -97,4 +139,3 @@ namespace ASC.Migrations.MySql.Migrations
name: "webhooks_config");
}
}
}

View File

@ -16,9 +16,40 @@ namespace ASC.Migrations.MySql.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.7")
.HasAnnotation("ProductVersion", "7.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.DbWebhook", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Method")
.ValueGeneratedOnAdd()
.HasMaxLength(10)
.HasColumnType("varchar(10)")
.HasColumnName("method")
.HasDefaultValueSql("''")
.IsRequired();
b.Property<string>("Route")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
.HasColumnType("varchar(50)")
.HasColumnName("route")
.HasDefaultValueSql("''")
.IsRequired();
b.HasKey("Id")
.HasName("PRIMARY");
b.ToTable("webhooks", (string)null);
b.HasAnnotation("MySql:CharSet", "utf8");
});
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b =>
{
b.Property<int>("Id")
@ -32,12 +63,6 @@ namespace ASC.Migrations.MySql.Migrations
.HasColumnName("enabled")
.HasDefaultValueSql("'1'");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)")
.HasColumnName("name");
b.Property<string>("SecretKey")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
@ -88,7 +113,7 @@ namespace ASC.Migrations.MySql.Migrations
b.Property<string>("Method")
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnType("varchar")
.HasColumnName("method");
b.Property<string>("RequestHeaders")
@ -112,10 +137,9 @@ namespace ASC.Migrations.MySql.Migrations
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<string>("Route")
.HasMaxLength(100)
.HasColumnType("varchar(100)")
.HasColumnName("route");
b.Property<int>("ConfigId")
.HasColumnType("int")
.HasColumnName("config_id");
b.Property<int>("Status")
.HasColumnType("int")

View File

@ -23,6 +23,33 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasAnnotation("ProductVersion", "6.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.DbWebhook", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Route")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("route");
b.Property<string>("Method")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("method");
b.HasKey("Id")
.HasName("PRIMARY");
b.ToTable("webhooks", (string)null);
b.HasAnnotation("MySql:CharSet", "utf8");
});
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b =>
{
b.Property<int>("Id")
@ -90,11 +117,6 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnType("datetime")
.HasColumnName("delivery");
b.Property<string>("Method")
.HasMaxLength(100)
.HasColumnType("varchar")
.HasColumnName("method");
b.Property<string>("RequestHeaders")
.HasColumnType("json")
.HasColumnName("request_headers");
@ -112,10 +134,9 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnType("text")
.HasColumnName("response_payload");
b.Property<string>("Route")
.HasMaxLength(100)
.HasColumnType("varchar")
.HasColumnName("route");
b.Property<int>("WebhookId")
.HasColumnType("int")
.HasColumnName("webhook_id");
b.Property<int>("Status")
.HasColumnType("int")

View File

@ -1,15 +1,55 @@
using System;
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace ASC.Migrations.PostgreSql.Migrations
{
namespace ASC.Migrations.PostgreSql.Migrations;
public partial class WebhooksDbContextMigrate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "webhooks",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
route = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false, defaultValueSql: "''"),
method = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: false, defaultValueSql: "''")
},
constraints: table =>
{
table.PrimaryKey("PRIMARY", x => x.id);
});
migrationBuilder.CreateTable(
name: "webhooks_config",
columns: table => new
@ -35,8 +75,7 @@ namespace ASC.Migrations.PostgreSql.Migrations
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
config_id = table.Column<int>(type: "int", nullable: false),
creation_time = table.Column<DateTime>(type: "datetime", nullable: false),
method = table.Column<string>(type: "varchar", maxLength: 100, nullable: true),
route = table.Column<string>(type: "varchar", maxLength: 100, nullable: true),
webhook_id = table.Column<int>(type: "int", nullable: false),
request_headers = table.Column<string>(type: "json", nullable: true),
request_payload = table.Column<string>(type: "text", nullable: false),
response_headers = table.Column<string>(type: "json", nullable: true),
@ -75,6 +114,9 @@ namespace ASC.Migrations.PostgreSql.Migrations
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "webhooks");
migrationBuilder.DropTable(
name: "webhooks_logs");
@ -82,4 +124,3 @@ namespace ASC.Migrations.PostgreSql.Migrations
name: "webhooks_config");
}
}
}

View File

@ -18,9 +18,37 @@ namespace ASC.Migrations.PostgreSql.Migrations
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "6.0.7")
.HasAnnotation("ProductVersion", "7.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.DbWebhook", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("Method")
.ValueGeneratedOnAdd()
.HasMaxLength(10)
.HasColumnType("character varying(10)")
.HasColumnName("method")
.HasDefaultValueSql("''");
b.Property<string>("Route")
.ValueGeneratedOnAdd()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("route")
.HasDefaultValueSql("''");
b.HasKey("Id")
.HasName("PRIMARY");
b.ToTable("webhooks", (string)null);
});
modelBuilder.Entity("ASC.Webhooks.Core.EF.Model.WebhooksConfig", b =>
{
b.Property<int>("Id")
@ -35,12 +63,6 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnName("enabled")
.HasDefaultValueSql("true");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("name");
b.Property<string>("SecretKey")
.ValueGeneratedOnAdd()
.HasMaxLength(50)
@ -80,6 +102,10 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnType("int")
.HasColumnName("config_id");
b.Property<int>("WebhookId")
.HasColumnType("int")
.HasColumnName("webhook_id");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime")
.HasColumnName("creation_time");
@ -88,11 +114,6 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnType("datetime")
.HasColumnName("delivery");
b.Property<string>("Method")
.HasMaxLength(100)
.HasColumnType("varchar")
.HasColumnName("method");
b.Property<string>("RequestHeaders")
.HasColumnType("json")
.HasColumnName("request_headers");
@ -110,11 +131,6 @@ namespace ASC.Migrations.PostgreSql.Migrations
.HasColumnType("text")
.HasColumnName("response_payload");
b.Property<string>("Route")
.HasMaxLength(100)
.HasColumnType("varchar")
.HasColumnName("route");
b.Property<int>("Status")
.HasColumnType("int")
.HasColumnName("status");

View File

@ -31,7 +31,7 @@
"LastModifiedBy": "Last modified by",
"Properties": "Properties",
"RoomsEmptyScreenTent": "See rooms details here",
"SelectedUsers": "Selected users",
"SelectedUsers": "Selected accounts",
"StorageType": "Storage type",
"SubmenuDetails": "Details",
"SubmenuHistory": "History",

View File

@ -1,15 +1,21 @@
import BreakpointWarningSvgUrl from "PUBLIC_DIR/images/breakpoint-warning.svg?url";
import BreakpointWarningSvgUrl from "PUBLIC_DIR/images/manage.access.rights.react.svg?url";
import BreakpointWarningSvgDarkUrl from "PUBLIC_DIR/images/manage.access.rights.dark.react.svg?url";
import React from "react";
import { Trans, withTranslation } from "react-i18next";
import StyledBreakpointWarning from "./sub-components/StyledBreakpointWarning";
import Loader from "./sub-components/loader";
import { inject, observer } from "mobx-react";
const BreakpointWarning = ({ t, sectionName, tReady }) => {
const BreakpointWarning = ({ t, sectionName, tReady, theme }) => {
return !tReady ? (
<Loader />
) : (
<StyledBreakpointWarning>
<img src={BreakpointWarningSvgUrl} />
<img
src={
theme.isBase ? BreakpointWarningSvgUrl : BreakpointWarningSvgDarkUrl
}
/>
<div className="description">
<div className="text-breakpoint">{t("BreakpointWarningText")}</div>
@ -24,4 +30,8 @@ const BreakpointWarning = ({ t, sectionName, tReady }) => {
);
};
export default withTranslation(["Settings"])(BreakpointWarning);
export default inject(({ auth }) => {
return {
theme: auth.settingsStore.theme,
};
})(observer(withTranslation(["Settings"])(BreakpointWarning)));

View File

@ -1,4 +1,5 @@
import EmptyScreenFilterAltSvgUrl from "PUBLIC_DIR/images/empty_screen_filter_alt.svg?url";
import EmptyScreenFilterAltDarkSvgUrl from "PUBLIC_DIR/images/empty_screen_filter_alt_dark.svg?url";
import ClearEmptyFilterSvgUrl from "PUBLIC_DIR/images/clear.empty.filter.svg?url";
import React from "react";
import { withTranslation } from "react-i18next";
@ -21,6 +22,7 @@ const EmptyFilterContainer = ({
isArchiveFolder,
isRoomsFolder,
setClearSearch,
theme,
}) => {
const subheadingText = t("EmptyFilterSubheadingText");
const descriptionText = isRooms
@ -63,18 +65,22 @@ const EmptyFilterContainer = ({
</div>
);
const imageSrc = theme.isBase
? EmptyScreenFilterAltSvgUrl
: EmptyScreenFilterAltDarkSvgUrl;
return (
<EmptyContainer
headerText={t("Common:NotFoundTitle")}
descriptionText={descriptionText}
imageSrc={EmptyScreenFilterAltSvgUrl}
imageSrc={imageSrc}
buttons={buttons}
/>
);
};
export default inject(
({ filesStore, selectedFolderStore, treeFoldersStore }) => {
({ auth, filesStore, selectedFolderStore, treeFoldersStore }) => {
const { isRoomsFolder, isArchiveFolder } = treeFoldersStore;
const isRooms = isRoomsFolder || isArchiveFolder;
@ -88,6 +94,7 @@ export default inject(
isArchiveFolder,
isRoomsFolder,
setClearSearch: filesStore.setClearSearch,
theme: auth.settingsStore.theme,
};
}
)(withTranslation(["Files", "Common"])(observer(EmptyFilterContainer)));

View File

@ -1,7 +1,9 @@
import PlusSvgUrl from "PUBLIC_DIR/images/plus.svg?url";
import PlusSvgUrl from "PUBLIC_DIR/images/plus.svg?url";
import UpSvgUrl from "PUBLIC_DIR/images/up.svg?url";
import EmptyScreenAltSvgUrl from "PUBLIC_DIR/images/empty_screen_alt.svg?url";
import EmptyScreenAltSvgDarkUrl from "PUBLIC_DIR/images/empty_screen_alt_dark.svg?url";
import EmptyScreenCorporateSvgUrl from "PUBLIC_DIR/images/empty_screen_corporate.svg?url";
import EmptyScreenCorporateDarkSvgUrl from "PUBLIC_DIR/images/empty_screen_corporate_dark.svg?url";
import { inject, observer } from "mobx-react";
import React from "react";
import { withTranslation } from "react-i18next";
@ -31,6 +33,7 @@ const EmptyFolderContainer = ({
isLoadedFetchFiles,
viewAs,
setIsLoadedEmptyPage,
theme,
}) => {
const onBackToParentFolder = () => {
setIsLoading(true);
@ -139,6 +142,13 @@ const EmptyFolderContainer = ({
<></>
);
const emptyScreenCorporateSvg = theme.isBase
? EmptyScreenCorporateSvgUrl
: EmptyScreenCorporateDarkSvgUrl;
const emptyScreenAltSvg = theme.isBase
? EmptyScreenAltSvgUrl
: EmptyScreenAltSvgDarkUrl;
if (!isLoadedFetchFiles || !tReady) {
return <Loaders.EmptyContainerLoader viewAs={viewAs} />;
}
@ -152,7 +162,7 @@ const EmptyFolderContainer = ({
? t("EmptyFolderDecription")
: t("EmptyFolderDescriptionUser")
}
imageSrc={isRooms ? EmptyScreenCorporateSvgUrl : EmptyScreenAltSvgUrl}
imageSrc={isRooms ? emptyScreenCorporateSvg : emptyScreenAltSvg}
buttons={buttons}
sectionWidth={sectionWidth}
isEmptyFolderContainer={true}
@ -162,6 +172,7 @@ const EmptyFolderContainer = ({
export default inject(
({
auth,
accessRightsStore,
filesStore,
selectedFolderStore,
@ -212,6 +223,7 @@ export default inject(
isLoadedFetchFiles,
viewAs,
setIsLoadedEmptyPage,
theme: auth.settingsStore.theme,
};
}
)(withTranslation(["Files", "Translations"])(observer(EmptyFolderContainer)));

View File

@ -1,5 +1,6 @@
import EmptyFolderImageSvgUrl from "PUBLIC_DIR/images/empty-folder-image.svg?url";
import ManageAccessRightsReactSvgUrl from "PUBLIC_DIR/images/manage.access.rights.react.svg?url";
import ManageAccessRightsReactSvgDarkUrl from "PUBLIC_DIR/images/manage.access.rights.dark.react.svg?url";
import React from "react";
import { inject, observer } from "mobx-react";
@ -23,6 +24,7 @@ const RoomNoAccessContainer = (props) => {
categoryType,
isEmptyPage,
sectionWidth,
theme,
} = props;
const descriptionRoomNoAccess = t("NoAccessRoomDescription");
@ -77,7 +79,9 @@ const RoomNoAccessContainer = (props) => {
const propsRoomNotFoundOrMoved = {
headerText: titleRoomNoAccess,
descriptionText: descriptionRoomNoAccess,
imageSrc: ManageAccessRightsReactSvgUrl,
imageSrc: theme.isBase
? ManageAccessRightsReactSvgUrl
: ManageAccessRightsReactSvgDarkUrl,
buttons: goToButtons,
};
@ -92,7 +96,7 @@ const RoomNoAccessContainer = (props) => {
);
};
export default inject(({ filesStore }) => {
export default inject(({ auth, filesStore }) => {
const {
setIsLoading,
fetchRooms,
@ -106,5 +110,6 @@ export default inject(({ filesStore }) => {
categoryType,
setAlreadyFetchingRooms,
isEmptyPage,
theme: auth.settingsStore.theme,
};
})(withTranslation(["Files"])(observer(RoomNoAccessContainer)));

View File

@ -19,12 +19,19 @@ import history from "@docspace/common/history";
import config from "PACKAGE_FILE";
import PlusIcon from "PUBLIC_DIR/images/plus.react.svg";
import EmptyScreenPersonalUrl from "PUBLIC_DIR/images/empty_screen_personal.svg?url";
import EmptyScreenPersonalDarkUrl from "PUBLIC_DIR/images/empty_screen_personal_dark.svg?url";
import EmptyScreenCorporateSvgUrl from "PUBLIC_DIR/images/empty_screen_corporate.svg?url";
import EmptyScreenCorporateDarkSvgUrl from "PUBLIC_DIR/images/empty_screen_corporate_dark.svg?url";
import EmptyScreenFavoritesUrl from "PUBLIC_DIR/images/empty_screen_favorites.svg?url";
import EmptyScreenFavoritesDarkUrl from "PUBLIC_DIR/images/empty_screen_favorites_dark.svg?url";
import EmptyScreenRecentUrl from "PUBLIC_DIR/images/empty_screen_recent.svg?url";
import EmptyScreenPrivacyUrl from "PUBLIC_DIR/images/empty_screen_privacy.png";
import EmptyScreenRecentDarkUrl from "PUBLIC_DIR/images/empty_screen_recent_dark.svg?url";
import EmptyScreenPrivacyUrl from "PUBLIC_DIR/images/empty_screen_privacy.svg?url";
import EmptyScreenPrivacyDarkUrl from "PUBLIC_DIR/images/empty_screen_privacy_dark.svg?url";
import EmptyScreenTrashSvgUrl from "PUBLIC_DIR/images/empty_screen_trash.svg?url";
import EmptyScreenTrashSvgDarkUrl from "PUBLIC_DIR/images/empty_screen_trash_dark.svg?url";
import EmptyScreenArchiveUrl from "PUBLIC_DIR/images/empty_screen_archive.svg?url";
import EmptyScreenArchiveDarkUrl from "PUBLIC_DIR/images/empty_screen_archive_dark.svg?url";
const StyledPlusIcon = styled(PlusIcon)`
path {
@ -146,27 +153,35 @@ const RootFolderContainer = (props) => {
return {
headerText: emptyScreenHeader,
descriptionText: personalDescription,
imageSrc: EmptyScreenPersonalUrl,
imageSrc: theme.isBase
? EmptyScreenPersonalUrl
: EmptyScreenPersonalDarkUrl,
buttons: commonButtons,
};
case FolderType.Favorites:
return {
headerText: noFilesHeader,
descriptionText: favoritesDescription,
imageSrc: EmptyScreenFavoritesUrl,
imageSrc: theme.isBase
? EmptyScreenFavoritesUrl
: EmptyScreenFavoritesDarkUrl,
buttons: isVisitor ? null : goToPersonalButtons,
};
case FolderType.Recent:
return {
headerText: noFilesHeader,
descriptionText: recentDescription,
imageSrc: EmptyScreenRecentUrl,
imageSrc: theme.isBase
? EmptyScreenRecentUrl
: EmptyScreenRecentDarkUrl,
buttons: isVisitor ? null : goToPersonalButtons,
};
case FolderType.Privacy:
return {
descriptionText: privateRoomDescription,
imageSrc: EmptyScreenPrivacyUrl,
imageSrc: theme.isBase
? EmptyScreenPrivacyUrl
: EmptyScreenPrivacyDarkUrl,
buttons: isDesktop && isEncryptionSupport && commonButtons,
};
case FolderType.TRASH:
@ -174,21 +189,27 @@ const RootFolderContainer = (props) => {
headerText: emptyScreenHeader,
descriptionText: trashDescription,
style: { gridColumnGap: "39px", gridTemplateColumns: "150px" },
imageSrc: EmptyScreenTrashSvgUrl,
imageSrc: theme.isBase
? EmptyScreenTrashSvgUrl
: EmptyScreenTrashSvgDarkUrl,
buttons: trashButtons,
};
case FolderType.Rooms:
return {
headerText: roomHeader,
descriptionText: roomsDescription,
imageSrc: EmptyScreenCorporateSvgUrl,
imageSrc: theme.isBase
? EmptyScreenCorporateSvgUrl
: EmptyScreenCorporateDarkSvgUrl,
buttons: isVisitor ? null : roomsButtons,
};
case FolderType.Archive:
return {
headerText: archiveHeader,
descriptionText: archiveRoomsDescription,
imageSrc: EmptyScreenArchiveUrl,
imageSrc: theme.isBase
? EmptyScreenArchiveUrl
: EmptyScreenArchiveDarkUrl,
buttons: archiveButtons,
};
default:

View File

@ -25,6 +25,18 @@ const ChangeUserTypeEvent = ({
} = peopleDialogData;
const { t } = useTranslation(["ChangeUserTypeDialog", "Common", "Payments"]);
const onKeyUpHandler = (e) => {
if (e.keyCode === 27) onCloseAction();
if (e.keyCode === 13) onChangeUserType();
};
useEffect(() => {
document.addEventListener("keyup", onKeyUpHandler, false);
return () => {
document.removeEventListener("keyup", onKeyUpHandler, false);
};
}, []);
useEffect(() => {
if (!peopleDialogData.toType) return;

View File

@ -1,5 +1,6 @@
import CatalogAccountsReactSvgUrl from "PUBLIC_DIR/images/catalog.accounts.react.svg?url";
import EmptyScreenPersonsSvgUrl from "PUBLIC_DIR/images/empty_screen_persons.svg?url";
import EmptyScreenPersonsSvgDarkUrl from "PUBLIC_DIR/images/empty_screen_persons_dark.svg?url";
import DefaultUserPhoto from "PUBLIC_DIR/images/default_user_photo_size_82-82.png";
import React, { useState, useEffect, useRef } from "react";
@ -24,7 +25,6 @@ const PeopleSelector = ({
className,
emptyScreenDescription,
emptyScreenHeader,
emptyScreenImage,
headerLabel,
id,
isMultiSelect,
@ -37,7 +37,6 @@ const PeopleSelector = ({
onSelectAll,
searchEmptyScreenDescription,
searchEmptyScreenHeader,
searchEmptyScreenImage,
searchPlaceholder,
selectAllIcon,
selectAllLabel,
@ -51,6 +50,7 @@ const PeopleSelector = ({
filter,
excludeItems,
currentUserId,
theme,
}) => {
const [itemsList, setItemsList] = useState(items);
const [searchValue, setSearchValue] = useState("");
@ -172,6 +172,10 @@ const PeopleSelector = ({
loadNextPage(0, "");
};
const emptyScreenImage = theme.isBase
? EmptyScreenPersonsSvgUrl
: EmptyScreenPersonsSvgDarkUrl;
return (
<Selector
id={id}
@ -200,8 +204,10 @@ const PeopleSelector = ({
emptyScreenImage={emptyScreenImage}
emptyScreenHeader={emptyScreenHeader || t("EmptyHeader")}
emptyScreenDescription={emptyScreenDescription || t("EmptyDescription")}
searchEmptyScreenImage={searchEmptyScreenImage}
searchEmptyScreenHeader={searchEmptyScreenHeader || t("NotFoundUsers")}
searchEmptyScreenImage={emptyScreenImage}
searchEmptyScreenHeader={
searchEmptyScreenHeader || t("People:NotFoundUsers")
}
searchEmptyScreenDescription={
searchEmptyScreenDescription || t("SearchEmptyDescription")
}
@ -227,8 +233,6 @@ PeopleSelector.propTypes = { excludeItems: PropTypes.array };
PeopleSelector.defaultProps = {
excludeItems: [],
selectAllIcon: CatalogAccountsReactSvgUrl,
emptyScreenImage: EmptyScreenPersonsSvgUrl,
searchEmptyScreenImage: EmptyScreenPersonsSvgUrl,
};
const ExtendedPeopleSelector = inject(({ auth }) => {
@ -238,9 +242,12 @@ const ExtendedPeopleSelector = inject(({ auth }) => {
};
})(
observer(
withTranslation(["PeopleSelector", "PeopleTranslations", "Common"])(
PeopleSelector
)
withTranslation([
"PeopleSelector",
"PeopleTranslations",
"People",
"Common",
])(PeopleSelector)
)
);

View File

@ -225,6 +225,10 @@ const StyledToggleButton = styled(ToggleButton)`
margin-top: -4px;
`;
const StyledText = styled(Text)`
flex-basis: 72%;
`;
export {
StyledBlock,
StyledHeading,
@ -247,4 +251,5 @@ export {
ScrollList,
StyledAccessSelector,
StyledToggleButton,
StyledText,
};

View File

@ -1,8 +1,6 @@
import InfoEditReactSvgUrl from "PUBLIC_DIR/images/info.edit.react.svg?url";
import AtReactSvgUrl from "PUBLIC_DIR/images/@.react.svg?url";
import React, { useState, useEffect } from "react";
import Text from "@docspace/components/text";
import Avatar from "@docspace/components/avatar";
import { parseAddresses } from "@docspace/components/utils/email";
@ -16,6 +14,7 @@ import {
StyledCrossIcon,
StyledHelpButton,
StyledDeleteIcon,
StyledText,
} from "../StyledInvitePanel";
const Item = ({
@ -110,9 +109,9 @@ const Item = ({
const displayBody = (
<>
<Text {...textProps} noSelect>
<StyledText {...textProps} truncate noSelect>
{inputValue}
</Text>
</StyledText>
{hasError ? (
<>
<StyledHelpButton

View File

@ -1,4 +1,5 @@
import EmptyScreenPersonSvgUrl from "PUBLIC_DIR/images/empty_screen_persons.svg?url";
import EmptyScreenPersonSvgDarkUrl from "PUBLIC_DIR/images/empty_screen_persons_dark.svg?url";
import ClearEmptyFilterSvgUrl from "PUBLIC_DIR/images/clear.empty.filter.svg?url";
import React from "react";
import { inject, observer } from "mobx-react";
@ -10,7 +11,7 @@ import Link from "@docspace/components/link";
import Box from "@docspace/components/box";
import Grid from "@docspace/components/grid";
const EmptyScreen = ({ resetFilter, isEmptyGroup, setIsLoading }) => {
const EmptyScreen = ({ resetFilter, isEmptyGroup, setIsLoading, theme }) => {
const { t } = useTranslation(["People", "Common"]);
const title = t("NotFoundUsers");
@ -21,10 +22,13 @@ const EmptyScreen = ({ resetFilter, isEmptyGroup, setIsLoading }) => {
resetFilter().finally(() => setIsLoading(false));
};
const imageSrc = theme.isBase
? EmptyScreenPersonSvgUrl
: EmptyScreenPersonSvgDarkUrl;
return (
<>
<EmptyScreenContainer
imageSrc={EmptyScreenPersonSvgUrl}
imageSrc={imageSrc}
imageAlt="Empty Screen Filter image"
headerText={title}
descriptionText={description}
@ -64,7 +68,7 @@ const EmptyScreen = ({ resetFilter, isEmptyGroup, setIsLoading }) => {
);
};
export default inject(({ peopleStore }) => {
export default inject(({ auth, peopleStore }) => {
const { loadingStore, resetFilter, selectedGroupStore } = peopleStore;
const { isEmptyGroup } = selectedGroupStore;
const { setIsLoading } = loadingStore;
@ -72,5 +76,6 @@ export default inject(({ peopleStore }) => {
resetFilter,
isEmptyGroup,
setIsLoading,
theme: auth.settingsStore.theme,
};
})(observer(EmptyScreen));

View File

@ -1,7 +1,7 @@
import styled from "styled-components";
const StyledNoItemContainer = styled.div`
margin: 80px auto;
//margin: 80px auto;
display: flex;
align-items: center;
justify-content: center;
@ -21,6 +21,10 @@ const StyledNoItemContainer = styled.div`
width: 96px;
height: 100px;
}
.no-accounts {
padding-top: 80px;
}
`;
export { StyledNoItemContainer };

View File

@ -2,8 +2,11 @@ import styled from "styled-components";
const StyledSeveralItemsContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 32px;
padding-top: ${(props) => (props.isAccounts ? "80px" : "0")};
`;
export { StyledSeveralItemsContainer };

View File

@ -13,17 +13,9 @@ const AccountsItemTitle = ({
isSeveralItems,
selection,
getUserContextOptions,
selectionLength,
}) => {
if (isSeveralItems) {
return (
<StyledTitle>
<Avatar size={"min"} role={"user"} />
<Text className="text" fontWeight={600} fontSize="16px">
{`${t("InfoPanel:SelectedUsers")}: ${selectionLength}`}
</Text>
</StyledTitle>
);
return <></>;
}
const itemTitleRef = useRef();

View File

@ -9,26 +9,10 @@ import ItemContextOptions from "./ItemContextOptions";
import { StyledTitle } from "../../styles/common";
const FilesItemTitle = ({
t,
selection,
isSeveralItems,
getIcon,
selectionLength,
}) => {
const FilesItemTitle = ({ t, selection, isSeveralItems }) => {
const itemTitleRef = useRef();
if (isSeveralItems)
return (
<StyledTitle>
<div className="item-icon">
<ReactSVG className="icon" src={getIcon(32, ".file")} />
</div>
<Text className="text">
{`${t("InfoPanel:ItemsSelected")}: ${selectionLength}`}
</Text>
</StyledTitle>
);
if (isSeveralItems) return <></>;
const icon = selection.icon;

View File

@ -1,14 +1,20 @@
import EmptyScreenAccountsInfoPanelPngUrl from "PUBLIC_DIR/images/empty_screen-accounts-info-panel.png";
import EmptyScreenPersonSvgUrl from "PUBLIC_DIR/images/empty_screen_persons.svg?url";
import EmptyScreenPersonSvgDarkUrl from "PUBLIC_DIR/images/empty_screen_persons_dark.svg?url";
import React from "react";
import { inject, observer } from "mobx-react";
import Text from "@docspace/components/text";
import { StyledNoItemContainer } from "../../styles/noItem";
const NoAccountsItem = ({ t }) => {
const NoAccountsItem = ({ t, theme }) => {
const imgSrc = theme.isBase
? EmptyScreenPersonSvgUrl
: EmptyScreenPersonSvgDarkUrl;
return (
<StyledNoItemContainer>
<div className="no-thumbnail-img-wrapper">
<img src={EmptyScreenAccountsInfoPanelPngUrl} />
<div className="no-thumbnail-img-wrapper no-accounts">
<img src={imgSrc} />
</div>
<Text className="no-item-text" textAlign="center">
{t("InfoPanel:AccountsEmptyScreenText")}
@ -17,4 +23,8 @@ const NoAccountsItem = ({ t }) => {
);
};
export default NoAccountsItem;
export default inject(({ auth }) => {
return {
theme: auth.settingsStore.theme,
};
})(observer(NoAccountsItem));

View File

@ -1,17 +1,17 @@
import EmptyScreenInfoPanelPngUrl from "PUBLIC_DIR/images/empty_screen_info_panel.png";
import EmptyScreenAltSvgUrl from "PUBLIC_DIR/images/empty_screen_alt.svg?url";
import EmptyScreenAltSvgDarkUrl from "PUBLIC_DIR/images/empty_screen_alt_dark.svg?url";
import React from "react";
import { inject, observer } from "mobx-react";
import { StyledNoItemContainer } from "../../styles/noItem";
const NoFileOrFolderItem = ({ t }) => {
const NoFileOrFolderItem = ({ t, theme }) => {
const imgSrc = theme.isBase ? EmptyScreenAltSvgUrl : EmptyScreenAltSvgDarkUrl;
return (
<StyledNoItemContainer>
<div className="no-thumbnail-img-wrapper">
<img
size="96px"
className="no-thumbnail-img"
src={EmptyScreenInfoPanelPngUrl}
/>
<img size="96px" className="no-thumbnail-img" src={imgSrc} />
</div>
<div className="no-item-text">{t("FilesEmptyScreenText")}</div>
@ -19,4 +19,8 @@ const NoFileOrFolderItem = ({ t }) => {
);
};
export default NoFileOrFolderItem;
export default inject(({ auth }) => {
return {
theme: auth.settingsStore.theme,
};
})(observer(NoFileOrFolderItem));

View File

@ -1,14 +1,21 @@
import InfoPanelRoomEmptyScreenSvgUrl from "PUBLIC_DIR/images/info-panel-room-empty-screen.svg?url";
import InfoPanelRoomEmptyScreenSvgUrl from "PUBLIC_DIR/images/empty_screen_corporate.svg?url";
import InfoPanelRoomEmptyScreenDarkSvgUrl from "PUBLIC_DIR/images/empty_screen_corporate_dark.svg?url";
import React from "react";
import { inject, observer } from "mobx-react";
import Text from "@docspace/components/text";
import { StyledNoItemContainer } from "../../styles/noItem";
const NoRoomItem = ({ t }) => {
const NoRoomItem = ({ t, theme }) => {
const imageSrc = theme.isBase
? InfoPanelRoomEmptyScreenSvgUrl
: InfoPanelRoomEmptyScreenDarkSvgUrl;
return (
<StyledNoItemContainer className="info-panel_gallery-empty-screen">
<div className="no-thumbnail-img-wrapper">
<img src={InfoPanelRoomEmptyScreenSvgUrl} alt="No Room Image" />
<img src={imageSrc} alt="No Room Image" />
</div>
<Text className="no-item-text" textAlign="center">
{t("RoomsEmptyScreenTent")}
@ -16,4 +23,9 @@ const NoRoomItem = ({ t }) => {
</StyledNoItemContainer>
);
};
export default NoRoomItem;
export default inject(({ auth }) => {
return {
theme: auth.settingsStore.theme,
};
})(observer(NoRoomItem));

View File

@ -1,18 +1,49 @@
import EmptyScreenAccountsInfoPanelPngUrl from "PUBLIC_DIR/images/empty_screen-accounts-info-panel.png";
import EmptyScreenInfoPanelPngUrl from "PUBLIC_DIR/images/empty_screen_info_panel.png";
import EmptyScreenPersonSvgUrl from "PUBLIC_DIR/images/empty_screen_persons.svg?url";
import EmptyScreenPersonSvgDarkUrl from "PUBLIC_DIR/images/empty_screen_persons_dark.svg?url";
import EmptyScreenAltSvgUrl from "PUBLIC_DIR/images/empty_screen_alt.svg?url";
import EmptyScreenAltSvgDarkUrl from "PUBLIC_DIR/images/empty_screen_alt_dark.svg?url";
import React from "react";
import { inject, observer } from "mobx-react";
import { useTranslation } from "react-i18next";
import Text from "@docspace/components/text";
import { StyledSeveralItemsContainer } from "../../styles/severalItems";
const SeveralItems = ({ isAccounts }) => {
const imgSrc = isAccounts
? EmptyScreenAccountsInfoPanelPngUrl
: EmptyScreenInfoPanelPngUrl;
const SeveralItems = ({ isAccounts, theme, selectedItems }) => {
const { t } = useTranslation("InfoPanel");
const emptyScreenAlt = theme.isBase
? EmptyScreenAltSvgUrl
: EmptyScreenAltSvgDarkUrl;
const emptyScreenPerson = theme.isBase
? EmptyScreenPersonSvgUrl
: EmptyScreenPersonSvgDarkUrl;
const imgSrc = isAccounts ? emptyScreenPerson : emptyScreenAlt;
const itemsText = isAccounts
? t("InfoPanel:SelectedUsers")
: t("InfoPanel:ItemsSelected");
return (
<StyledSeveralItemsContainer className="no-thumbnail-img-wrapper">
<StyledSeveralItemsContainer
isAccounts={isAccounts}
className="no-thumbnail-img-wrapper"
>
<img size="96px" src={imgSrc} />
<Text fontSize="16px" fontWeight={700}>
{`${itemsText}: ${selectedItems.length}`}
</Text>
</StyledSeveralItemsContainer>
);
};
export default SeveralItems;
export default inject(({ auth }) => {
const selectedItems = auth.infoPanelStore.getSelectedItems();
return {
theme: auth.settingsStore.theme,
selectedItems,
};
})(observer(SeveralItems));

View File

@ -26,10 +26,12 @@ const RoomCell = ({ sideColor, item }) => {
setIsTooltipLoading(false);
};
const canVisibleTitle = originRoomTitle || originTitle;
return [
<StyledText
key="cell"
fontSize="12px"
fontSize={canVisibleTitle ? "12px" : "13px"}
fontWeight={600}
color={sideColor}
className="row_update-text"
@ -38,7 +40,7 @@ const RoomCell = ({ sideColor, item }) => {
data-tip={""}
data-place={"bottom"}
>
{originRoomTitle || originTitle}
{originRoomTitle || originTitle || "—"}
</StyledText>,
<Tooltip

View File

@ -36,11 +36,13 @@ const elementResizeDetector = elementResizeDetectorMaker({
const FilesTileContainer = ({ filesList, t, sectionWidth, withPaging }) => {
const tileRef = useRef(null);
const timerRef = useRef(null);
const isMountedRef = useRef(true);
const [thumbSize, setThumbSize] = useState("");
const [columnCount, setColumnCount] = useState(null);
useEffect(() => {
return () => {
isMountedRef.current = false;
if (!tileRef?.current) return;
clearTimeout(timerRef.current);
elementResizeDetector.uninstall(tileRef.current);
@ -49,7 +51,7 @@ const FilesTileContainer = ({ filesList, t, sectionWidth, withPaging }) => {
const onResize = useCallback(
(node) => {
if (!node) return;
if (!node || !isMountedRef.current) return;
const { width } = node.getBoundingClientRect();

View File

@ -1,20 +0,0 @@
import ManageAccessRightsReactSvgUrl from "PUBLIC_DIR/images/manage.access.rights.react.svg?url";
import React from "react";
import { useTranslation } from "react-i18next";
import EmptyScreenContainer from "@docspace/components/empty-screen-container";
const ForbiddenPage = () => {
const { t } = useTranslation("Settings");
return (
<EmptyScreenContainer
descriptionText={t("ForbiddenPageDescription")}
headerText={t("ForbiddenPageHeader")}
imageAlt="Empty screen image"
imageSrc={ManageAccessRightsReactSvgUrl}
/>
);
};
export default ForbiddenPage;

View File

@ -140,7 +140,7 @@ class AccountsContextOptionsStore {
key: option,
icon: InfoOutlineReactSvgUrl,
label: t("Common:Info"),
onClick: this.onDetailsClick,
onClick: () => this.onDetailsClick(item),
};
case "invite-again":
@ -389,8 +389,10 @@ class AccountsContextOptionsStore {
setDeleteProfileDialogVisible(true);
};
onDetailsClick = () => {
onDetailsClick = (item) => {
const { setIsVisible } = this.peopleStore.infoPanelStore;
const { setBufferSelection } = this.peopleStore.selectionStore;
setBufferSelection(item);
setIsVisible(true);
};

View File

@ -514,7 +514,7 @@ class ContextOptionsStore {
}
}
return options;
return options.filter((o) => !!o);
};
onShowInfoPanel = (item) => {
@ -589,8 +589,8 @@ class ContextOptionsStore {
const hasInfoPanel = contextOptions.includes("show-info");
const emailSendIsDisabled = true;
const showSeparator0 = hasInfoPanel || !isMedia || !emailSendIsDisabled;
//const emailSendIsDisabled = true;
const showSeparator0 = hasInfoPanel || !isMedia; // || !emailSendIsDisabled;
const separator0 = showSeparator0
? {
@ -873,13 +873,13 @@ class ContextOptionsStore {
onClick: () => this.onCopyLink(item, t),
disabled: false,
},
{
id: "option_send-by-email",
key: "send-by-email",
label: t("SendByEmail"),
icon: MailReactSvgUrl,
disabled: emailSendIsDisabled,
},
// {
// id: "option_send-by-email",
// key: "send-by-email",
// label: t("SendByEmail"),
// icon: MailReactSvgUrl,
// disabled: emailSendIsDisabled,
// },
...versionActions,
{
id: "option_show-info",
@ -1048,6 +1048,15 @@ class ContextOptionsStore {
});
}
}
if (options[0]?.isSeparator) {
options.shift();
}
if (options[options.length - 1]?.isSeparator) {
options.pop();
}
return options;
};

View File

@ -100,6 +100,8 @@ class FilesStore {
createdItem = null;
scrollToItem = null;
roomCreated = false;
isLoadingFilesFind = false;
pageItemsLength = null;
isHidePagination = false;
@ -179,6 +181,8 @@ class FilesStore {
const newFiles = [fileInfo, ...this.files];
if (this.files.findIndex((x) => x.id === opt?.id) > -1) return;
if (newFiles.length > this.filter.pageCount && withPaging) {
newFiles.pop(); // Remove last
}
@ -192,11 +196,18 @@ class FilesStore {
});
} else if (opt?.type === "folder" && opt?.id) {
const foundIndex = this.folders.findIndex((x) => x.id === opt?.id);
if (foundIndex > -1) return;
const folder = JSON.parse(opt?.data);
if (this.selectedFolderStore.id !== folder.parentId) return;
if (
this.selectedFolderStore.id !== folder.parentId ||
(folder.roomType &&
folder.createdBy.id === this.authStore.userStore.user.id &&
this.roomCreated)
)
return (this.roomCreated = false);
const folderInfo = await api.files.getFolderInfo(folder.id);
@ -877,10 +888,16 @@ class FilesStore {
let newSelections = JSON.parse(JSON.stringify(this.selection));
for (let item of added) {
if (!item) return;
const value =
this.viewAs === "tile"
? item.getAttribute("value")
: item.getElementsByClassName("files-item")[0].getAttribute("value");
: item.getElementsByClassName("files-item")
? item.getElementsByClassName("files-item")[0]?.getAttribute("value")
: null;
if (!value) return;
const splitValue = value && value.split("_");
const fileType = splitValue[0];
@ -910,10 +927,14 @@ class FilesStore {
}
for (let item of removed) {
if (!item) return;
const value =
this.viewAs === "tile"
? item.getAttribute("value")
: item.getElementsByClassName("files-item")[0].getAttribute("value");
: item.getElementsByClassName("files-item")
? item.getElementsByClassName("files-item")[0]?.getAttribute("value")
: null;
const splitValue = value && value.split("_");
@ -1964,9 +1985,10 @@ class FilesStore {
return api.files.createFolder(parentFolderId, title);
}
createRoom(roomParams) {
createRoom = (roomParams) => {
this.roomCreated = true;
return api.rooms.createRoom(roomParams);
}
};
createRoomInThirdpary(thirpartyFolderId, roomParams) {
return api.rooms.createRoomInThirdpary(thirpartyFolderId, roomParams);

View File

@ -0,0 +1,22 @@
import styled from "styled-components";
import { Base } from "@docspace/components/themes";
const ControlBtn = styled.div`
display: inline-block;
height: 30px;
line-height: 25px;
margin: 5px;
width: 40px;
border-radius: 2px;
cursor: pointer;
text-align: center;
&:hover {
background-color: ${(props) =>
props.theme.mediaViewer.controlBtn.backgroundColor};
}
`;
ControlBtn.defaultProps = { theme: Base };
export default ControlBtn;

View File

@ -0,0 +1,15 @@
import styled from "styled-components";
type StyledButtonScrollProps = {
orientation: "left" | "right";
};
const StyledButtonScroll = styled.div<StyledButtonScrollProps>`
z-index: 307;
position: fixed;
top: calc(50% - 20px);
${(props) => (props.orientation === "left" ? "left: 20px;" : "right: 20px;")}
`;
export default StyledButtonScroll;

View File

@ -0,0 +1,47 @@
import styled from "styled-components";
const StyledMobileDetails = styled.div`
z-index: 307;
position: fixed;
top: 0;
left: 0;
right: 0;
height: 53px;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(
0deg,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.8) 100%
);
svg {
path {
fill: #fff;
}
}
.mobile-close {
position: fixed;
left: 21px;
top: 22px;
}
.mobile-context {
position: fixed;
right: 22px;
top: 22px;
}
.title {
font-weight: 600;
margin-top: 6px;
width: calc(100% - 100px);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
`;
export default StyledMobileDetails;

View File

@ -0,0 +1,23 @@
import styled from "styled-components";
type StyledSwitchToolbarProps = {
left?: boolean;
};
const StyledSwitchToolbar = styled.div<StyledSwitchToolbarProps>`
height: 100%;
z-index: 306;
position: fixed;
width: 73px;
background: inherit;
display: block;
opacity: 0;
transition: all 0.3s;
${(props) => (props.left ? "left: 0" : "right: 0")};
&:hover {
cursor: pointer;
opacity: 1;
}
`;
export default StyledSwitchToolbar;

View File

@ -0,0 +1,84 @@
import styled from "styled-components";
import { Base } from "@docspace/components/themes";
type StyledViewerContainerProps = {
visible: boolean;
};
const StyledViewerContainer = styled.div<StyledViewerContainerProps>`
color: ${(props) => props.theme.mediaViewer.color};
display: ${(props) => (props.visible ? "block" : "none")};
overflow: hidden;
span {
position: fixed;
right: 0;
bottom: 5px;
margin-right: 10px;
z-index: 305;
}
.deleteBtnContainer,
.downloadBtnContainer {
display: block;
width: 16px;
height: 16px;
margin: 4px 12px;
line-height: 19px;
svg {
path {
fill: ${(props) => props.theme.mediaViewer.fill};
}
}
}
.details {
z-index: 307;
padding-top: 21px;
height: 64px;
width: 100%;
background: linear-gradient(
0deg,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.8) 100%
);
position: fixed;
top: 0;
left: 0;
.title {
text-align: center;
white-space: nowrap;
overflow: hidden;
font-size: 20px;
font-weight: 600;
text-overflow: ellipsis;
width: calc(100% - 50px);
padding-left: 16px;
box-sizing: border-box;
color: ${(props) => props.theme.mediaViewer.titleColor};
}
}
.mediaPlayerClose {
position: fixed;
top: 13px;
right: 12px;
height: 17px;
&:hover {
background-color: transparent;
}
svg {
path {
fill: ${(props) => props.theme.mediaViewer.iconColor};
}
}
}
.containerVideo {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
}
`;
StyledViewerContainer.defaultProps = { theme: Base };
export default StyledViewerContainer;

View File

@ -0,0 +1,5 @@
export { default as ControlBtn } from "./ControlBtn";
export { default as StyledButtonScroll } from "./StyledButtonScroll";
export { default as StyledSwitchToolbar } from "./StyledSwitchToolbar";
export { default as StyledViewerContainer } from "./StyledViewerContainer";
export { default as StyledMobileDetails } from "./StyledMobileDetails";

View File

@ -64,5 +64,5 @@ export const findNearestIndex = (
};
export const isSeparator = (arg: ContextMenuModel): arg is SeparatorType => {
return arg?.isSeparator;
return arg?.isSeparator !== undefined && arg.isSeparator;
};

View File

@ -1,4 +1,4 @@
import { isMobileOnly } from "react-device-detect";
import { isMobile } from "react-device-detect";
import React, { useState, useCallback, useMemo, useEffect } from "react";
import ViewerWrapper from "./sub-components/ViewerWrapper";
@ -229,9 +229,9 @@ function MediaViewer({
},
];
return isMobileOnly
return isMobile
? model
: isImage && !isMobileOnly
: isImage && !isMobile
? desktopModel.filter((el) => el.key !== "download")
: desktopModel;
};

View File

@ -0,0 +1,15 @@
import { ContextMenuModel } from "../../types";
interface MobileDetailsProps {
icon: string;
title: string;
isError: boolean;
isPreviewFile: boolean;
contextModel: () => ContextMenuModel[];
onHide: VoidFunction;
onMaskClick: VoidFunction;
onContextMenu: (e: TouchEvent) => void;
}
export default MobileDetailsProps;

View File

@ -0,0 +1,70 @@
import React, { ForwardedRef, useMemo } from "react";
import Text from "@docspace/components/text";
import ContextMenu from "@docspace/components/context-menu";
import { StyledMobileDetails } from "../../StyledComponents";
import type MobileDetailsProps from "./MobileDetails.props";
import BackArrow from "PUBLIC_DIR/images/viewer.media.back.react.svg";
import MediaContextMenu from "PUBLIC_DIR/images/vertical-dots.react.svg";
function MobileDetails(
{
icon,
title,
isError,
isPreviewFile,
onHide,
onMaskClick,
onContextMenu,
contextModel,
}: MobileDetailsProps,
ref: ForwardedRef<ContextMenu>
) {
const contextMenuHeader = useMemo(
() => ({
icon: icon,
title: title,
}),
[icon, title]
);
return (
<StyledMobileDetails>
<BackArrow className="mobile-close" onClick={onMaskClick} />
<Text
fontSize="14px"
color="#fff"
className="title"
as={undefined}
tag={undefined}
title={undefined}
textAlign={undefined}
fontWeight={undefined}
>
{title}
</Text>
{!isPreviewFile && !isError && (
<div className="details-context">
<MediaContextMenu
className="mobile-context"
onClick={onContextMenu}
/>
<ContextMenu
ref={ref}
withBackdrop={true}
onHide={onHide}
header={contextMenuHeader}
getContextModel={contextModel}
/>
</div>
)}
</StyledMobileDetails>
);
}
export default React.memo(
React.forwardRef<ContextMenu, MobileDetailsProps>(MobileDetails)
);

View File

@ -0,0 +1,23 @@
import React from "react";
import {
StyledButtonScroll,
StyledSwitchToolbar,
} from "../../StyledComponents";
import MediaNextIcon from "PUBLIC_DIR/images/viewer.next.react.svg";
type NextButtonProps = {
nextClick: VoidFunction;
};
function NextButton({ nextClick }: NextButtonProps) {
return (
<StyledSwitchToolbar onClick={nextClick}>
<StyledButtonScroll orientation="right">
<MediaNextIcon />
</StyledButtonScroll>
</StyledSwitchToolbar>
);
}
export default NextButton;

View File

@ -0,0 +1,23 @@
import React from "react";
import {
StyledButtonScroll,
StyledSwitchToolbar,
} from "../../StyledComponents";
import MediaPrevIcon from "PUBLIC_DIR/images/viewer.prew.react.svg";
type PrevButtonProps = {
prevClick: VoidFunction;
};
function PrevButton({ prevClick }: PrevButtonProps) {
return (
<StyledSwitchToolbar left onClick={prevClick}>
<StyledButtonScroll orientation="left">
<MediaPrevIcon />
</StyledButtonScroll>
</StyledSwitchToolbar>
);
}
export default PrevButton;

View File

@ -0,0 +1,36 @@
import { getCustomToolbar } from "../../helpers/getCustomToolbar";
import { ContextMenuModel, PlaylistType } from "../../types";
interface ViewerProps {
title: string;
images: { src: string; alt: string }[];
isAudio: boolean;
isVideo: boolean;
visible: boolean;
isImage: boolean;
playlist: PlaylistType[];
inactive: boolean;
audioIcon: string;
zoomSpeed: number;
errorTitle: string;
headerIcon: string;
customToolbar: () => ReturnType<typeof getCustomToolbar>;
playlistPos: number;
archiveRoom: boolean;
isPreviewFile: boolean;
onMaskClick: VoidFunction;
onNextClick: VoidFunction;
onPrevClick: VoidFunction;
contextModel: () => ContextMenuModel[];
onDownloadClick: VoidFunction;
generateContextMenu: (
isOpen: boolean,
right: string,
bottom: string
) => JSX.Element;
onSetSelectionFile: VoidFunction;
}
export default ViewerProps;

View File

@ -0,0 +1,215 @@
import ReactDOM from "react-dom";
import React, { useRef, useState, useEffect, useCallback } from "react";
import { isMobileOnly } from "react-device-detect";
import Text from "@docspace/components/text";
import IconButton from "@docspace/components/icon-button";
import ContextMenu from "@docspace/components/context-menu";
import { StyledViewer } from "@docspace/components/viewer/styled-viewer";
import ViewerPlayer from "@docspace/components/viewer/sub-components/viewer-player";
import { ControlBtn, StyledViewerContainer } from "../../StyledComponents";
import MobileDetails from "../MobileDetails";
import PrevButton from "../PrevButton";
import NextButton from "../NextButton";
import type ViewerProps from "./Viewer.props";
import ViewerMediaCloseSvgUrl from "PUBLIC_DIR/images/viewer.media.close.svg?url";
function Viewer(props: ViewerProps) {
const timerIDRef = useRef<NodeJS.Timeout>();
const containerRef = React.useRef(document.createElement("div"));
const [panelVisible, setPanelVisible] = useState<boolean>(true);
const [isOpenContextMenu, setIsOpenContextMenu] = useState<boolean>(false);
const [isError, setIsError] = useState<boolean>(false);
const [isPlay, setIsPlay] = useState<boolean | null>(null);
const [imageTimer, setImageTimer] = useState<NodeJS.Timeout>();
const contextMenuRef = useRef<ContextMenu>(null);
const videoElementRef = useRef<HTMLVideoElement>(null);
const [isFullscreen, setIsFullScreen] = useState<boolean>(false);
useEffect(() => {
document.body.appendChild(containerRef.current);
return () => {
document.body.removeChild(containerRef.current);
timerIDRef.current && clearTimeout(timerIDRef.current);
};
}, []);
useEffect(() => {
if ((!isPlay || isOpenContextMenu) && (!props.isImage || isOpenContextMenu))
return clearTimeout(timerIDRef.current);
}, [isPlay, isOpenContextMenu, props.isImage]);
useEffect(() => {
if (isMobileOnly) return;
const resetTimer = () => {
setPanelVisible(true);
clearTimeout(timerIDRef.current);
timerIDRef.current = setTimeout(() => setPanelVisible(false), 2500);
setImageTimer(timerIDRef.current);
};
document.addEventListener("mousemove", resetTimer, { passive: true });
return () => {
document.removeEventListener("mousemove", resetTimer);
clearTimeout(timerIDRef.current);
setPanelVisible(true);
};
}, [setImageTimer, setPanelVisible]);
useEffect(() => {
document.addEventListener("touchstart", onTouch);
return () => document.removeEventListener("touchstart", onTouch);
}, [setPanelVisible]);
const onTouch = useCallback(
(e: TouchEvent, canTouch?: boolean) => {
if (e.target === videoElementRef.current || canTouch) {
setPanelVisible((visible) => !visible);
}
},
[setPanelVisible]
);
const nextClick = () => {
clearTimeout(imageTimer);
props.onNextClick();
};
const prevClick = () => {
clearTimeout(imageTimer);
props.onPrevClick();
};
const onMobileContextMenu = useCallback(
(e: TouchEvent) => {
setIsOpenContextMenu((open) => !open);
props.onSetSelectionFile();
contextMenuRef.current?.show(e);
},
[props.onSetSelectionFile, setIsOpenContextMenu]
);
const onHide = useCallback(() => {
setIsOpenContextMenu(false);
}, [setIsOpenContextMenu]);
const mobileDetails = (
<MobileDetails
isError={isError}
title={props.title}
icon={props.headerIcon}
contextModel={props.contextModel}
isPreviewFile={props.isPreviewFile}
onHide={onHide}
onContextMenu={onMobileContextMenu}
onMaskClick={props.onMaskClick}
ref={contextMenuRef}
/>
);
const displayUI = (isMobileOnly && props.isAudio) || panelVisible;
const isNotFirstElement = props.playlistPos !== 0;
const isNotLastElement = props.playlistPos < props.playlist.length - 1;
return (
<StyledViewerContainer visible={props.visible}>
{!isFullscreen && !isMobileOnly && displayUI && (
<div>
<div className="details">
<Text
isBold
fontSize="14px"
className="title"
title={undefined}
tag={undefined}
as={undefined}
fontWeight={undefined}
color={undefined}
textAlign={undefined}
>
{props.title}
</Text>
<ControlBtn
onClick={props.onMaskClick}
className="mediaPlayerClose"
>
<IconButton
color={"#fff"}
iconName={ViewerMediaCloseSvgUrl}
size={28}
isClickable
/>
</ControlBtn>
</div>
</div>
)}
{props.playlist.length > 1 && !isFullscreen && displayUI && (
<>
{isNotFirstElement && <PrevButton prevClick={prevClick} />}
{isNotLastElement && <NextButton nextClick={nextClick} />}
</>
)}
{props.isImage
? ReactDOM.createPortal(
<StyledViewer
{...props}
displayUI={displayUI}
mobileDetails={mobileDetails}
setIsOpenContextMenu={setIsOpenContextMenu}
container={containerRef.current}
imageTimer={imageTimer}
onMaskClick={props.onMaskClick}
setPanelVisible={setPanelVisible}
generateContextMenu={props.generateContextMenu}
/>,
containerRef.current
)
: (props.isVideo || props.isAudio) &&
ReactDOM.createPortal(
<ViewerPlayer
{...props}
onNextClick={nextClick}
onPrevClick={prevClick}
isAudio={props.isAudio}
audioIcon={props.audioIcon}
contextModel={props.contextModel}
mobileDetails={mobileDetails}
displayUI={displayUI}
isOpenContextMenu={isOpenContextMenu}
onTouch={onTouch}
title={props.title}
setIsPlay={setIsPlay}
setIsOpenContextMenu={setIsOpenContextMenu}
isPlay={isPlay}
onMaskClick={props.onMaskClick}
setPanelVisible={setPanelVisible}
generateContextMenu={props.generateContextMenu}
setIsFullScreen={setIsFullScreen}
setIsError={setIsError}
videoRef={videoElementRef}
video={props.playlist[props.playlistPos]}
activeIndex={props.playlistPos}
/>,
containerRef.current
)}
</StyledViewerContainer>
);
}
export default Viewer;

View File

@ -1,7 +1,7 @@
import React, { useMemo, memo, useCallback } from "react";
import equal from "fast-deep-equal/react";
import { Viewer } from "@docspace/components/viewer";
import Viewer from "../Viewer";
import { isSeparator } from "../../helpers";
import { getCustomToolbar } from "../../helpers/getCustomToolbar";
import { ContextMenuModel } from "../../types";

View File

@ -1,24 +0,0 @@
import React from "react";
export default function Duration({ className, seconds }) {
return (
<time dateTime={`P${Math.round(seconds)}S`} className={className}>
{format(seconds)}
</time>
);
}
function format(seconds) {
const date = new Date(seconds * 1000);
const hh = date.getUTCHours();
const mm = date.getUTCMinutes();
const ss = pad(date.getUTCSeconds());
if (hh) {
return `${hh}:${pad(mm)}:${ss}`;
}
return `${mm}:${ss}`;
}
function pad(string) {
return ("0" + string).slice(-2);
}

View File

@ -1,415 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import MediaZoomInIcon from "PUBLIC_DIR/images/media.zoomin.react.svg";
import MediaZoomOutIcon from "PUBLIC_DIR/images/media.zoomout.react.svg";
import MediaRotateLeftIcon from "PUBLIC_DIR/images/media.rotateleft.react.svg";
import MediaRotateRightIcon from "PUBLIC_DIR/images/media.rotateright.react.svg";
import MediaDeleteIcon from "PUBLIC_DIR/images/media.delete.react.svg";
import MediaDownloadIcon from "PUBLIC_DIR/images/download.react.svg";
import commonIconsStyles from "@docspace/components/utils/common-icons-style";
import MediaFavoriteIcon from "PUBLIC_DIR/images/favorite.react.svg";
import ViewerSeparator from "PUBLIC_DIR/images/viewer.separator.react.svg";
import MediaShare from "PUBLIC_DIR/images/share.react.svg";
import DropDownItem from "@docspace/components/drop-down-item";
import DropDown from "@docspace/components/drop-down";
import equal from "fast-deep-equal/react";
import { Base } from "@docspace/components/themes";
import { Viewer } from "@docspace/components/viewer";
const StyledViewer = styled(Viewer)`
.react-viewer-footer {
bottom: 5px !important;
z-index: 301 !important;
overflow: visible;
}
.react-viewer-canvas {
z-index: 300 !important;
margin-top: 50px;
}
.react-viewer-navbar,
.react-viewer-mask,
.react-viewer-attribute,
.react-viewer-close {
display: none;
}
.react-viewer-toolbar {
position: relative;
overflow: visible;
bottom: 4px;
}
.react-viewer-toolbar li {
width: 40px;
height: 30px;
margin-top: 4px;
border-radius: 2px;
cursor: pointer;
line-height: 24px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.react-viewer-btn {
background-color: transparent;
&:hover {
background-color: ${(props) =>
props.theme.mediaViewer.imageViewer.backgroundColor};
}
}
.react-viewer-image-transition {
transition-duration: 0s;
}
li[data-key="prev"] {
left: 20px;
}
li[data-key="next"] {
right: 20px;
}
li[data-key="prev"],
li[data-key="next"] {
position: fixed;
top: calc(50% - 20px);
height: auto;
background: none;
&:hover {
background: none;
}
}
li[data-key="delete"],
li[data-key="customDownload"] {
position: fixed;
@media (max-width: 600px) {
position: initial;
}
bottom: 9px;
.controlBtn {
margin: 0;
}
}
li[data-key="delete"] {
right: 62px;
}
li[data-key="customDownload"] {
right: 12px;
}
.iconContainer {
width: 24px;
height: 24px;
line-height: 20px;
margin: 3px auto;
&.reset {
width: 18px;
}
path,
rect {
fill: ${(props) => props.theme.mediaViewer.imageViewer.fill};
}
}
.btnContainer {
display: block;
width: 16px;
height: 16px;
margin: 4px 12px;
line-height: 19px;
path,
rect {
fill: ${(props) => props.theme.mediaViewer.imageViewer.fill};
}
}
.scrollBtn {
cursor: ${(props) => (props.inactive ? "default" : "pointer")};
opacity: ${(props) => (props.inactive ? "0.2" : "1")};
&:hover {
background-color: ${(props) =>
!props.inactive
? props.theme.mediaViewer.imageViewer.backgroundColor
: props.theme.mediaViewer.imageViewer.inactiveBackgroundColor};
}
}
`;
const StyledDropDown = styled(DropDown)`
background: #333;
`;
const StyledDropDownItem = styled(DropDownItem)`
color: #fff;
.drop-down-item_icon svg {
path {
fill: #fff !important;
}
}
/* .is-separator {
height: 1px;
background: #474747;
} */
&:hover {
background: #444;
}
`;
StyledViewer.defaultProps = { theme: Base };
class ImageViewer extends React.Component {
// componentDidUpdate() {
// document.getElementsByClassName("iconContainer reset").length > 0 &&
// document.getElementsByClassName("iconContainer reset")[0].click();
// }
shouldComponentUpdate(nextProps) {
return !equal(this.props, nextProps);
}
render() {
const {
className,
visible,
images,
inactive,
onClose,
userAccess,
title,
errorTitle,
onPrevClick,
onNextClick,
playlist,
playlistPos,
isImage,
isAudio,
isVideo,
isPreviewFile,
archiveRoom,
contextModel,
audioIcon,
headerIcon,
onSetSelectionFile,
onDownloadClick,
} = this.props;
const generateContextMenu = (isOpen, right, bottom) => {
const model = contextModel();
const onItemClick = (e, item) => {
const { action, onClick } = item;
return onClick({ originalEvent: e, action: action, item });
};
return (
<StyledDropDown
open={isOpen}
isDefaultMode={false}
directionY="top"
directionX="right"
fixedDirection={true}
withBackdrop={false}
manualY={(bottom || "63") + "px"}
manualX={(right || "-31") + "px"}
>
{model.map((item) => {
if (item.disabled) return;
const onClick = (e) => {
onClose();
onItemClick(e, item);
};
return (
<StyledDropDownItem
className={`${item.isSeparator ? "is-separator" : ""}`}
key={item.key}
label={item.label}
icon={item.icon ? item.icon : ""}
action={item.action}
onClick={onClick}
/>
);
})}
</StyledDropDown>
);
};
var customToolbar = [
{
key: "zoomOut",
percent: true,
actionType: 2,
render: (
<div className="iconContainer zoomOut">
<MediaZoomOutIcon size="scale" />
</div>
),
},
{
key: "percent",
actionType: 999,
},
{
key: "zoomIn",
actionType: 1,
render: (
<div className="iconContainer zoomIn">
<MediaZoomInIcon size="scale" />
</div>
),
},
{
key: "rotateLeft",
actionType: 5,
render: (
<div className="iconContainer rotateLeft">
<MediaRotateLeftIcon size="scale" />
</div>
),
},
{
key: "rotateRight",
actionType: 6,
render: (
<div className="iconContainer rotateRight">
<MediaRotateRightIcon size="scale" />
</div>
),
},
{
key: "separator download-separator",
actionType: -1,
noHover: true,
render: (
<div className="separator" style={{ height: "16px" }}>
<ViewerSeparator size="scale" />
</div>
),
},
// {
// key: "share",
// actionType: 101,
// render: (
// <div className="iconContainer share" style={{ height: "16px" }}>
// <MediaShare size="scale" />
// </div>
// ),
// },
{
key: "download",
actionType: 102,
render: (
<div className="iconContainer download" style={{ height: "16px" }}>
<MediaDownloadIcon size="scale" />
</div>
),
},
{
key: "context-separator",
actionType: -1,
noHover: true,
render: (
<div className="separator" style={{ height: "16px" }}>
<ViewerSeparator size="scale" />
</div>
),
},
{
key: "context-menu",
actionType: -1,
},
{
key: "delete",
actionType: 103,
render: (
<div className="iconContainer viewer-delete">
<MediaDeleteIcon size="scale" />
</div>
),
},
{
key: "favorite",
actionType: 104,
render: (
<div className="iconContainer viewer-favorite">
<MediaFavoriteIcon size="scale" />
</div>
),
},
];
customToolbar.forEach((button) => {
switch (button.key) {
case "prev":
button.onClick = this.props.onPrevClick;
break;
case "next":
button.onClick = this.props.onNextClick;
break;
case "delete":
button.onClick = this.props.onDeleteClick;
break;
case "download":
button.onClick = onDownloadClick;
break;
default:
break;
}
});
const canShare = playlist[playlistPos].canShare;
const toolbars =
!canShare && userAccess
? customToolbar.filter(
(x) => x.key !== "share" && x.key !== "share-separator"
)
: customToolbar.filter((x) => x.key !== "delete");
return (
<div className={className}>
<Viewer
inactive={inactive}
visible={visible}
zoomSpeed={0.25}
title={title}
errorTitle={errorTitle}
contextModel={contextModel}
generateContextMenu={generateContextMenu}
isImage={isImage}
headerIcon={headerIcon}
isAudio={isAudio}
isVideo={isVideo}
isPreviewFile={isPreviewFile}
archiveRoom={archiveRoom}
audioIcon={audioIcon}
onSetSelectionFile={onSetSelectionFile}
onMaskClick={onClose}
onNextClick={onNextClick}
onPrevClick={onPrevClick}
onDownloadClick={onDownloadClick}
playlist={playlist}
playlistPos={playlistPos}
customToolbar={() => toolbars}
images={images}
/>
</div>
);
}
}
ImageViewer.propTypes = {
className: PropTypes.string,
visible: PropTypes.bool,
inactive: PropTypes.bool,
images: PropTypes.arrayOf(PropTypes.object),
onNextClick: PropTypes.func,
onPrevClick: PropTypes.func,
onDeleteClick: PropTypes.func,
onDownloadClick: PropTypes.func,
onClose: PropTypes.func,
};
export default ImageViewer;

View File

@ -1,138 +0,0 @@
import React from "react";
import styled from "styled-components";
import PropTypes from "prop-types";
import { Base } from "@docspace/components/themes";
const StyledProgress = styled.div`
display: inline-block;
.slider-container {
display: inline-block;
border-radius: 2px;
position: relative;
width: ${(props) => props.width}px;
height: 6px;
background: ${(props) =>
props.theme.mediaViewer.progressBar.backgroundColor};
margin: 15px 0;
vertical-align: middle;
}
.fill {
cursor: pointer;
width: ${(props) => 100 * props.value}%;
position: absolute;
top: calc(50% - 3px);
height: 6px;
background: ${(props) => props.theme.mediaViewer.progressBar.background};
border-radius: 2px;
}
input[type="range"] {
display: block;
overflow: visible;
background: transparent;
width: ${(props) => props.width}px;
height: 6px;
outline: none;
margin: 0;
-webkit-appearance: none;
position: relative;
cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
position: relative;
appearance: none;
box-sizing: content-box;
width: 12px;
height: 12px;
margin-top: -3px;
background: white;
border-radius: 50%;
cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
position: relative;
appearance: none;
box-sizing: content-box;
width: 12px;
height: 12px;
background: white;
border-radius: 50%;
margin-top: -3px;
cursor: pointer;
}
input[type="range"]::-ms-thumb {
position: relative;
appearance: none;
box-sizing: content-box;
width: 12px;
height: 12px;
background: white;
border-radius: 50%;
margin-top: -3px;
cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
margin: 12px 0;
height: 6px;
border-radius: 2px;
cursor: pointer;
-webkit-appearance: none;
text-align: right;
pointer-events: none;
}
input[type="range"]::-moz-range-track {
margin: 12px 0;
height: 6px;
border-radius: 2px;
cursor: pointer;
-webkit-appearance: none;
text-align: right;
pointer-events: none;
}
input[type="range"]::-ms-track {
border-color: transparent;
color: transparent;
margin: 12px 0;
height: 6px;
border-radius: 2px;
cursor: pointer;
-webkit-appearance: none;
text-align: right;
pointer-events: none;
}
`;
StyledProgress.defaultProps = { theme: Base };
const Progress = (props) => {
return (
<StyledProgress {...props}>
<div className="slider-container">
<div className="fill"></div>
<input
type="range"
min={0}
max={0.999999}
step="any"
value={props.value}
onMouseDown={props.handleSeekMouseDown}
onChange={(event) => props.handleSeekChange(event)}
onMouseUp={props.handleSeekMouseUp}
/>
</div>
</StyledProgress>
);
};
Progress.propTypes = {
value: PropTypes.number,
handleSeekMouseDown: PropTypes.func,
handleSeekChange: PropTypes.func,
handleSeekMouseUp: PropTypes.func,
};
export default Progress;

View File

@ -1,579 +0,0 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import styled, { css } from "styled-components";
import { findDOMNode } from "react-dom";
import screenfull from "screenfull";
import ReactPlayer from "react-player";
import Duration from "./duration";
import Progress from "./progress";
import MediaPauseIcon from "PUBLIC_DIR/images/media.pause.react.svg";
import MediaPlayIcon from "PUBLIC_DIR/images/media.play.react.svg";
import MediaFullScreenIcon from "PUBLIC_DIR/images/media.fullscreen.video.react.svg";
import MediaMuteIcon from "PUBLIC_DIR/images/media.mute.react.svg";
import MediaMuteOffIcon from "PUBLIC_DIR/images/media.muteoff.react.svg";
import commonIconsStyles from "@docspace/components/utils/common-icons-style";
import { Base } from "@docspace/components/themes";
const iconsStyles = css`
path,
stroke,
rect {
fill: ${(props) => props.theme.mediaViewer.videoViewer.fill};
}
`;
const controlsHeight = 40;
const StyledControls = styled.div`
height: ${(props) => props.height}px;
display: block;
position: fixed;
z-index: 301;
${(props) =>
!props.isVideo &&
`background-color: ${props.theme.mediaViewer.videoViewer.backgroundColor};`}
top: calc(50% + ${(props) => props.top}px);
left: ${(props) => props.left}px;
`;
StyledControls.defaultProps = { theme: Base };
const StyledVideoControlBtn = styled.div`
display: inline-block;
height: 26px;
line-height: 30px;
margin: 5px 2px;
width: 38px;
border-radius: 2px;
cursor: pointer;
text-align: center;
vertical-align: top;
&:hover {
background-color: ${(props) =>
props.theme.mediaViewer.videoViewer.background};
}
.playBtnContainer {
width: 16px;
height: 16px;
line-height: 0;
margin: 5px auto;
}
.pauseBtnContainer {
display: block;
width: 16px;
height: 16px;
margin: 3px 10px;
line-height: 19px;
}
.muteBtnContainer {
display: block;
width: 16px;
height: 16px;
margin: 3px 11px;
line-height: 19px;
}
.fullscreenBtnContainer {
display: block;
width: 16px;
height: 16px;
margin: 3px 11px;
line-height: 19px;
}
`;
StyledVideoControlBtn.defaultProps = { theme: Base };
const StyledMediaPauseIcon = styled(MediaPauseIcon)`
${commonIconsStyles}
${iconsStyles}
`;
StyledMediaPauseIcon.defaultProps = { theme: Base };
const StyledMediaPlayIcon = styled(MediaPlayIcon)`
${commonIconsStyles}
${iconsStyles}
`;
StyledMediaPlayIcon.defaultProps = { theme: Base };
const StyledMediaFullScreenIcon = styled(MediaFullScreenIcon)`
${commonIconsStyles}
${iconsStyles}
`;
StyledMediaFullScreenIcon.defaultProps = { theme: Base };
const StyledMediaMuteIcon = styled(MediaMuteIcon)`
${commonIconsStyles}
path:first-child {
stroke: ${(props) => props.theme.mediaViewer.videoViewer.stroke};
}
path:last-child {
fill: ${(props) => props.theme.mediaViewer.videoViewer.fill};
}
`;
const StyledMediaMuteOffIcon = styled(MediaMuteOffIcon)`
${commonIconsStyles}
path, rect {
fill: ${(props) => props.theme.mediaViewer.videoViewer.fill};
}
`;
StyledMediaMuteIcon.defaultProps = { theme: Base };
const VideoControlBtn = (props) => {
return (
<StyledVideoControlBtn {...props}>{props.children}</StyledVideoControlBtn>
);
};
VideoControlBtn.propTypes = {
children: PropTypes.any,
};
const Controls = (props) => {
return <StyledControls {...props}>{props.children}</StyledControls>;
};
Controls.propTypes = {
children: PropTypes.any,
};
const PlayBtn = (props) => {
return (
<VideoControlBtn onClick={props.onClick}>
{props.playing ? (
<div className="pauseBtnContainer">
<StyledMediaPauseIcon size="scale" />
</div>
) : (
<div className="playBtnContainer">
<StyledMediaPlayIcon size="scale" />
</div>
)}
</VideoControlBtn>
);
};
PlayBtn.propTypes = {
playing: PropTypes.bool,
onClick: PropTypes.func,
};
const FullScreenBtn = (props) => {
return (
<VideoControlBtn onClick={props.onClick}>
<div className="fullscreenBtnContainer">
<StyledMediaFullScreenIcon size="scale" />
</div>
</VideoControlBtn>
);
};
FullScreenBtn.propTypes = {
onClick: PropTypes.func,
};
const StyledValumeContainer = styled.div`
display: inline-block;
vertical-align: top;
line-height: 39px;
position: relative;
.muteConteiner {
display: none;
position: absolute;
width: 40px;
height: 80px;
border-radius: 5px;
top: -76px;
left: 5px;
background: black;
}
&:hover {
.muteConteiner {
display: inline-block;
}
}
.mute {
display: inline-block;
transform: rotate(-90deg);
margin: 22px -14px;
}
`;
const StyledDuration = styled.div`
display: inline-block;
height: 26px;
line-height: 26px;
margin: 5px;
width: 60px;
text-align: center;
vertical-align: top;
border-radius: 2px;
cursor: pointer;
&:hover {
background-color: ${(props) =>
props.theme.mediaViewer.videoViewer.background};
}
`;
StyledValumeContainer.defaultProps = { theme: Base };
const StyledVideoViewer = styled.div`
color: ${(props) => props.theme.mediaViewer.videoViewer.color};
.playerWrapper {
display: ${(props) => (props.isVideo ? "block" : "none")};
width: ${(props) => props.width}px;
height: ${(props) => props.height}px;
left: ${(props) => props.left}px;
top: calc(50% - ${(props) => props.top / 2}px);
z-index: 301;
position: fixed;
padding-bottom: 40px;
background-color: ${(props) =>
props.theme.mediaViewer.videoViewer.backgroundColor};
video {
z-index: 300;
}
}
`;
StyledVideoViewer.defaultProps = { theme: Base };
const ErrorContainer = styled.div`
z-index: 301;
display: block;
position: fixed;
left: calc(50% - 110px);
top: calc(50% - 40px);
background-color: ${(props) =>
props.theme.mediaViewer.videoViewer.backgroundColorError};
color: ${(props) => props.theme.mediaViewer.videoViewer.colorError};
border-radius: 10px;
padding: 20px;
text-align: center;
`;
ErrorContainer.defaultProps = { theme: Base };
class ValumeBtn extends Component {
constructor(props) {
super(props);
}
render() {
return (
<StyledValumeContainer>
<div className="muteConteiner">
<Progress
className="mute"
width={this.props.width}
value={this.props.volume}
onMouseDown={this.props.onMouseDown}
handleSeekChange={this.props.onChange}
onMouseUp={this.props.handleSeekMouseUp}
/>
</div>
<VideoControlBtn onClick={this.props.onChangeMute}>
<div className="muteBtnContainer">
{this.props.muted ? (
<StyledMediaMuteOffIcon size="scale" />
) : (
<StyledMediaMuteIcon size="scale" />
)}
</div>
</VideoControlBtn>
</StyledValumeContainer>
);
}
}
ValumeBtn.propTypes = {
width: PropTypes.number,
volume: PropTypes.number,
muted: PropTypes.bool,
onMouseDown: PropTypes.func,
onChange: PropTypes.func,
handleSeekMouseUp: PropTypes.func,
onChangeMute: PropTypes.func,
};
class VideoViewer extends Component {
state = {
url: this.props.url,
pip: false,
playing: false,
controls: false,
light: false,
volume: 0.3,
muted: false,
played: 0,
loaded: 0,
duration: 0,
playbackRate: 1.0,
loop: false,
isNew: false,
error: false,
isLoaded: false,
};
componentDidMount() {
document.addEventListener("keydown", this.onKeydown, false);
}
componentWillUnmount() {
document.removeEventListener("keydown", this.onKeydown, false);
}
componentDidUpdate(prevProps) {
if (this.props.url !== prevProps.url) {
this.setState({
url: this.props.url,
isNew: true,
error: false,
});
}
}
onKeydown = (e) => {
if (e.keyCode === 32) this.handlePlayPause();
};
handlePlayPause = () => {
this.setState({ playing: !this.state.playing, isNew: false });
};
handleStop = () => {
this.setState({ url: null, playing: false });
};
handleVolumeChange = (e) => {
this.setState({
volume: parseFloat(e.target.value),
muted: false,
});
};
handleToggleMuted = () => {
this.setState({ muted: !this.state.muted });
};
handlePlay = () => {
this.setState({ playing: true });
};
handleEnablePIP = () => {
this.setState({ pip: true });
};
handleDisablePIP = () => {
this.setState({ pip: false });
};
handlePause = () => {
this.setState({ playing: false });
};
handleSeekMouseDown = (e) => {
this.setState({ seeking: true });
};
handleSeekChange = (e) => {
this.setState({ played: parseFloat(e.target.value) });
};
handleSeekMouseUp = (e) => {
console.log(!isNaN(parseFloat(e.target.value)), parseFloat(e.target.value));
if (!isNaN(parseFloat(e.target.value))) {
this.setState({ seeking: false });
this.player.seekTo(parseFloat(e.target.value));
}
};
handleProgress = (state) => {
if (!this.state.seeking) {
this.setState(state);
}
};
handleEnded = () => {
this.setState({ playing: this.state.loop });
};
handleDuration = (duration) => {
this.setState({ duration });
};
handleClickFullscreen = () => {
screenfull.request(findDOMNode(this.player));
};
ref = (player) => {
this.player = player;
};
resizePlayer = (videoSize, screenSize) => {
var ratio = videoSize.h / videoSize.w;
if (videoSize.h > screenSize.h) {
videoSize.h = screenSize.h;
videoSize.w = videoSize.h / ratio;
}
if (videoSize.w > screenSize.w) {
videoSize.w = screenSize.w;
videoSize.h = videoSize.w * ratio;
}
return {
width: videoSize.w,
height: videoSize.h,
};
};
onError = (e) => {
console.log("onError", e);
this.setState({ error: true });
};
onPlay = () => {
this.setState({ playing: !this.state.isNew, isNew: false, isLoaded: true });
};
render() {
const {
url,
playing,
controls,
light,
volume,
muted,
loop,
played,
loaded,
duration,
playbackRate,
pip,
error,
isLoaded,
} = this.state;
const { errorLabel } = this.props;
const parentOffset = this.props.getOffset() || 0;
var screenSize = {
w: window.innerWidth,
h: window.innerHeight,
};
screenSize.h -= parentOffset + controlsHeight;
let width = screenSize.w;
let height = screenSize.h;
let centerAreaOx = screenSize.w / 2 + document.documentElement.scrollLeft;
let centerAreaOy = screenSize.h / 2 + document.documentElement.scrollTop;
let videoElement = document.getElementsByTagName("video")[0];
if (videoElement) {
width = this.props.isVideo
? videoElement.videoWidth || 480
: screenSize.w - 150;
height = this.props.isVideo ? videoElement.videoHeight || 270 : 0;
let resize = this.resizePlayer(
{
w: width,
h: height,
},
screenSize
);
width = resize.width;
height = resize.height;
}
let left = this.props.isVideo
? centerAreaOx - width / 2
: centerAreaOx - width / 2;
const videoControlBtnWidth = 220;
const audioControlBtnWidth = 170;
let progressWidth = this.props.isVideo
? width - videoControlBtnWidth
: width - audioControlBtnWidth;
if (error) {
return (
<ErrorContainer>
<p>{errorLabel}</p>
</ErrorContainer>
);
}
return (
<StyledVideoViewer
isVideo={this.props.isVideo}
width={width}
height={height}
left={left}
top={height + controlsHeight}
>
<div>
<div className="playerWrapper" onClick={this.handlePlayPause}>
<ReactPlayer
ref={this.ref}
className="react-player"
style={{ opacity: isLoaded ? 1 : 0 }}
width="100%"
height="100%"
url={url}
pip={pip}
playing={playing}
playsinline={true}
controls={controls}
light={light}
loop={loop}
playbackRate={playbackRate}
volume={volume}
muted={muted}
onPlay={this.onPlay}
onEnablePIP={this.handleEnablePIP}
onDisablePIP={this.handleDisablePIP}
onPause={this.handlePause}
onEnded={this.handleEnded}
onError={this.onError}
onProgress={this.handleProgress}
onDuration={this.handleDuration}
/>
</div>
<Controls
height={controlsHeight}
left={left}
top={height / 2 - controlsHeight / 2}
isVideo={this.props.isVideo}
>
<PlayBtn onClick={this.handlePlayPause} playing={playing} />
<Progress
value={played}
width={progressWidth}
onMouseDown={this.handleSeekMouseDown}
handleSeekChange={this.handleSeekChange}
onMouseUp={this.handleSeekMouseUp}
onTouchEnd={this.handleSeekMouseUp}
/>
<StyledDuration>
-<Duration seconds={duration * (1 - played)} />
</StyledDuration>
<ValumeBtn
width={64}
muted={muted}
volume={muted ? 0 : volume}
onChangeMute={this.handleToggleMuted}
onChange={this.handleVolumeChange}
/>
{this.props.isVideo && (
<FullScreenBtn onClick={this.handleClickFullscreen} />
)}
</Controls>
</div>
</StyledVideoViewer>
);
}
}
VideoViewer.propTypes = {
isVideo: PropTypes.bool,
url: PropTypes.string,
getOffset: PropTypes.func,
errorLabel: PropTypes.string,
};
export default VideoViewer;

View File

@ -271,7 +271,7 @@ class SelectionArea extends React.Component {
if (e.target.tagName === "A") {
const node = e.target.closest("." + selectableClass);
onMove && onMove({ added: [node], removed: [], clear: true });
node && onMove && onMove({ added: [node], removed: [], clear: true });
return;
}

View File

@ -2330,7 +2330,7 @@ const Dark = {
},
emptyScreen: {
descriptionColor: cyanBlueDarkShade,
descriptionColor: "#ADADAD",
},
},

View File

@ -11,7 +11,7 @@ import ControlBtn from "./sub-components/control-btn";
import Text from "@docspace/components/text";
import IconButton from "@docspace/components/icon-button";
import { isMobileOnly } from "react-device-detect";
import { isMobile } from "react-device-detect";
import ViewerMediaCloseSvgUrl from "PUBLIC_DIR/images/viewer.media.close.svg?url";
import MediaNextIcon from "PUBLIC_DIR/images/viewer.next.react.svg";
@ -70,7 +70,7 @@ export const Viewer = (props) => {
if ((!isPlay || isOpenContextMenu) && (!isImage || isOpenContextMenu))
return clearTimeout(timer);
document.addEventListener("touchstart", onTouch);
if (!isMobileOnly) {
if (!isMobile) {
document.addEventListener("mousemove", resetTimer);
return () => {
@ -157,7 +157,7 @@ export const Viewer = (props) => {
</StyledMobileDetails>
);
const displayUI = (isMobileOnly && isAudio) || panelVisible;
const displayUI = (isMobile && isAudio) || panelVisible;
const viewerPortal = ReactDOM.createPortal(
<StyledViewer
@ -207,7 +207,7 @@ export const Viewer = (props) => {
return (
<StyledViewerContainer visible={visible}>
{!isFullscreen && !isMobileOnly && displayUI && (
{!isFullscreen && !isMobile && displayUI && (
<div>
<div className="details" ref={detailsContainerRef}>
<Text isBold fontSize="14px" className="title">
@ -228,7 +228,7 @@ export const Viewer = (props) => {
</div>
)}
{playlist.length > 1 && !isFullscreen && displayUI && (
{playlist.length > 1 && !isFullscreen && displayUI && !isMobile && (
<>
{playlistPos !== 0 && (
<StyledSwitchToolbar left onClick={prevClick}>

View File

@ -1,7 +1,7 @@
import * as React from "react";
import styled from "styled-components";
import Text from "@docspace/components/text";
import { isMobileOnly } from "react-device-detect";
import { isMobile } from "react-device-detect";
import { ReactSVG } from "react-svg";
const StyledMediaError = styled.div`
@ -54,7 +54,7 @@ export const MediaError = ({
let errorLeft = (window.innerWidth - width) / 2 + "px";
let errorTop = (window.innerHeight - height) / 2 + "px";
const items = !isMobileOnly
const items = !isMobile
? model.filter((el) => el.key !== "rename")
: model.filter((el) => el.key === "delete" || el.key === "download");

View File

@ -62,8 +62,8 @@ function MobileViewer({
height,
x: left,
y: top,
})
},[height, width])
});
}, [height, width]);
React.useEffect(() => {
unmountRef.current = false;
@ -321,6 +321,7 @@ function MobileViewer({
}
},
},
{
drag: {
from: () => [style.x.get(), style.y.get()],

View File

@ -2,7 +2,7 @@ import * as React from "react";
import ViewerImage from "./viewer-image";
import classnames from "classnames";
import ViewerToolbar, { defaultToolbars } from "./viewer-toolbar";
import { isMobileOnly } from "react-device-detect";
import { isMobile } from "react-device-detect";
import Icon, { ActionType } from "./icon";
const ACTION_TYPES = {
@ -219,13 +219,12 @@ const ViewerBase = (props) => {
if (imageRef.current) {
//abort previous image request
imageRef.current.src = ""
imageRef.current.src = "";
}
let loadComplete = false;
let img = new Image();
imageRef.current = img
imageRef.current = img;
img.src = activeImage.src;
img.onload = () => {
@ -455,7 +454,9 @@ const ViewerBase = (props) => {
document[funcName]("keydown", handleKeydown, true);
}
if (viewerCore.current) {
viewerCore.current[funcName]("wheel", handleMouseScroll, {passive: true});
viewerCore.current[funcName]("wheel", handleMouseScroll, {
passive: true,
});
}
}
@ -654,12 +655,13 @@ const ViewerBase = (props) => {
}}
ref={viewerCore}
>
{isMobileOnly && !displayVisible && mobileDetails}
{isMobile && !displayVisible && mobileDetails}
<div
className={`${prefixCls}-mask`}
style={{
zIndex: zIndex,
backgroundColor: `${isMobileOnly
backgroundColor: `${
isMobile
? !displayVisible
? "rgba(55,55,55,0.6)"
: "#000"
@ -685,6 +687,7 @@ const ViewerBase = (props) => {
opacity={state.opacity}
getImageCenterXY={getImageCenterXY}
setPanelVisible={props.setPanelVisible}
handleDefaultAction={handleDefaultAction}
handleZoom={handleZoom}
handleResetZoom={handleResetZoom}
height={state.height}
@ -708,7 +711,7 @@ const ViewerBase = (props) => {
container={props.container}
/>
{props.noFooter ||
(!isMobileOnly && props.displayUI && (
(!isMobile && props.displayUI && (
<div className={`${prefixCls}-container`}>
<div
className={`${prefixCls}-footer`}
@ -716,7 +719,7 @@ const ViewerBase = (props) => {
>
{noToolbar || (
<ViewerToolbar
isMobileOnly={isMobileOnly}
isMobileOnly={isMobile}
imageTimer={props.imageTimer}
prefixCls={prefixCls}
onAction={handleAction}

View File

@ -2,7 +2,8 @@ import * as React from "react";
import classnames from "classnames";
import ViewerLoading from "./viewer-loading";
import { useSwipeable } from "../../react-swipeable";
import { isMobileOnly } from "react-device-detect";
import { isIOS, isMobile } from "react-device-detect";
import { ActionType } from "./icon";
import MobileViewer from "./mobile-viewer";
function ViewerImage(props) {
@ -366,6 +367,10 @@ function ViewerImage(props) {
}
};
const handleClick = (e) => {
if (e?.detail === 2) props.handleDefaultAction(ActionType.zoomIn);
};
function handleResize(e) {
props.onResize();
}
@ -375,7 +380,11 @@ function ViewerImage(props) {
}
function onClose(e) {
if (e.target === imgRef.current || e.nativeEvent.pointerType === "touch")
if (
e.target === imgRef.current ||
e.nativeEvent.pointerType === "touch" ||
e.type === "touchend"
)
return;
props.onMaskClick();
}
@ -422,7 +431,7 @@ translateX(${props.left !== null ? props.left + "px" : "auto"}) translateY(${
let imgNode = null;
if (props.imgSrc !== "") {
imgNode = isMobileOnly ? (
imgNode = isMobile ? (
<MobileViewer
className={imgClass}
src={props.imgSrc}
@ -444,6 +453,7 @@ translateX(${props.left !== null ? props.left + "px" : "auto"}) translateY(${
style={imgStyle}
ref={imgRef}
onMouseDown={handleMouseDown}
onClick={handleClick}
/>
);
}
@ -462,12 +472,14 @@ translateX(${props.left !== null ? props.left + "px" : "auto"}) translateY(${
);
}
if (isMobileOnly) {
if (isMobile) {
const events = isIOS ? { onTouchEnd: onClose } : { onClick: onClose };
return (
<div
className={`${props.prefixCls}-canvas`}
onClick={onClose}
style={styleIndex}
{...events}
>
{imgNode}
</div>

View File

@ -356,9 +356,6 @@ export default function ViewerPlayer(props) {
setPanelVisible,
onTouch,
isOpenContextMenu,
setGlobalTimer,
globalTimer,
videoControls,
setIsOpenContextMenu,
contextModel,
onDownloadClick,
@ -418,10 +415,12 @@ export default function ViewerPlayer(props) {
const inputRef = React.useRef(null);
const volumeRef = React.useRef(null);
const actionRef = React.useRef(null);
const videoControls = React.useRef(null);
const mobileProgressRef = React.useRef(null);
const [state, dispatch] = React.useReducer(reducer, initialState);
const [currentVolume, setCurrentVolume] = React.useState(stateVolume);
const [globalTimer, setGlobalTimer] = React.useState(null);
const speedIcons = [<Icon05x />, <Icon1x />, <Icon15x />, <Icon2x />];
const handlers = useSwipeable({
onSwiping: (e) => {

View File

@ -3208,7 +3208,7 @@ public class FileStorageService<T> //: IFileStorageService
{
var (fileIntIds, _) = FileOperationsManager.GetIds(fileIds);
_eventBus.Publish(new ThumbnailRequestedIntegrationEvent(Guid.Empty, _tenantManager.GetCurrentTenant().Id)
_eventBus.Publish(new ThumbnailRequestedIntegrationEvent(_authContext.CurrentAccount.ID, _tenantManager.GetCurrentTenant().Id)
{
BaseUrl = _baseCommonLinkUtility.GetFullAbsolutePath(""),
FileIds = fileIntIds

View File

@ -61,7 +61,7 @@ namespace ASC.Files.Core.Resources {
}
/// <summary>
/// Looks up a localized string similar to My archive.
/// Looks up a localized string similar to Archive.
/// </summary>
public static string Archive {
get {
@ -169,7 +169,7 @@ namespace ASC.Files.Core.Resources {
}
/// <summary>
/// Looks up a localized string similar to My rooms.
/// Looks up a localized string similar to Rooms.
/// </summary>
public static string VirtualRooms {
get {

View File

@ -118,7 +118,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Archive" xml:space="preserve">
<value>My archive</value>
<value>Archive</value>
</data>
<data name="CorporateFiles" xml:space="preserve">
<value>Common</value>
@ -154,6 +154,6 @@
<value>Trash</value>
</data>
<data name="VirtualRooms" xml:space="preserve">
<value>My rooms</value>
<value>Rooms</value>
</data>
</root>

View File

@ -41,7 +41,7 @@ public abstract class ApiControllerBase : ControllerBase
_fileDtoHelper = fileDtoHelper;
}
public async Task<FileEntryDto> GetFileEntryWrapperAsync(FileEntry r)
protected async Task<FileEntryDto> GetFileEntryWrapperAsync(FileEntry r)
{
FileEntryDto wrapper = null;
if (r.FileEntryType == FileEntryType.Folder)

View File

@ -33,17 +33,23 @@ public class SettingsController : ApiControllerBase
private readonly FileStorageService<string> _fileStorageServiceString;
private readonly FilesSettingsHelper _filesSettingsHelper;
private readonly ProductEntryPoint _productEntryPoint;
private readonly SettingsManager _settingsManager;
private readonly TenantManager _tenantManager;
public SettingsController(
FileStorageService<string> fileStorageServiceString,
FilesSettingsHelper filesSettingsHelper,
ProductEntryPoint productEntryPoint,
FolderDtoHelper folderDtoHelper,
FileDtoHelper fileDtoHelper) : base(folderDtoHelper, fileDtoHelper)
FileDtoHelper fileDtoHelper,
SettingsManager settingsManager,
TenantManager tenantManager) : base(folderDtoHelper, fileDtoHelper)
{
_fileStorageServiceString = fileStorageServiceString;
_filesSettingsHelper = filesSettingsHelper;
_productEntryPoint = productEntryPoint;
_settingsManager = settingsManager;
_tenantManager = tenantManager;
}
/// <summary>

View File

@ -66,16 +66,14 @@ try
startup.ConfigureServices(builder.Services);
builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
startup.ConfigureContainer(containerBuilder);
});
builder.Host.ConfigureContainer<ContainerBuilder>(startup.ConfigureContainer);
var app = builder.Build();
startup.Configure(app, app.Environment);
logger.Info("Starting web host ({applicationContext})...", AppName);
await app.RunWithTasksAsync();
}
catch (Exception ex)

View File

@ -33,7 +33,7 @@ public class Startup : BaseStartup
public Startup(IConfiguration configuration, IHostEnvironment hostEnvironment)
: base(configuration, hostEnvironment)
{
WebhooksEnabled = true;
}
public override void ConfigureServices(IServiceCollection services)

View File

@ -32,6 +32,7 @@ public class Startup : BaseStartup
public Startup(IConfiguration configuration, IHostEnvironment hostEnvironment) : base(configuration, hostEnvironment)
{
WebhooksEnabled = true;
}
public override void ConfigureServices(IServiceCollection services)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -0,0 +1,17 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 55.4999H17.5V54.9999V31V30.5H17H11C9.04108 30.5 7.41455 31.3808 6.28494 32.5842C5.16343 33.7789 4.5 35.3257 4.5 36.7152V54.9999V55.4999H5H17Z" fill="#ADADAD" stroke="#333333"/>
<path d="M81 30.5H80.5V31V46.9999V47.4999H81H93H93.5V46.9999V36.2096C93.5 32.6762 90.5593 30.5 87.6667 30.5H81Z" fill="#ADADAD" stroke="#333333"/>
<path d="M74.6665 2.5H75.1665V3V56.0001V56.5001H74.6665H11H10.5V56.0001V15.3678V15.1588L10.6488 15.0119L23.1794 2.64414L23.3254 2.5H23.5306H74.6665Z" fill="#333333"/>
<path d="M74.6665 2.5H75.1665V3V56.0001V56.5001H74.6665H11H10.5V56.0001V15.3678V15.1588L10.6488 15.0119L23.1794 2.64414L23.3254 2.5H23.5306H74.6665Z" fill="white" fill-opacity="0.88"/>
<path d="M74.6665 2.5H75.1665V3V56.0001V56.5001H74.6665H11H10.5V56.0001V15.3678V15.1588L10.6488 15.0119L23.1794 2.64414L23.3254 2.5H23.5306H74.6665Z" stroke="#333333"/>
<path d="M22.6464 2.64645C22.7894 2.50345 23.0045 2.46067 23.1913 2.53806C23.3782 2.61545 23.5 2.79777 23.5 3V15C23.5 15.2761 23.2761 15.5 23 15.5H11C10.7978 15.5 10.6155 15.3782 10.5381 15.1913C10.4607 15.0045 10.5034 14.7894 10.6464 14.6464L22.6464 2.64645Z" fill="#E07751" stroke="#333333" stroke-linejoin="round"/>
<path d="M87 12.5H87.5V13V67V67.5H87H23H22.5V67V25.6011V25.3941L22.6464 25.2476L35.2426 12.6465L35.3891 12.5H35.5962H87Z" fill="#333333"/>
<path d="M87 12.5H87.5V13V67V67.5H87H23H22.5V67V25.6011V25.3941L22.6464 25.2476L35.2426 12.6465L35.3891 12.5H35.5962H87Z" fill="white" fill-opacity="0.88"/>
<path d="M87 12.5H87.5V13V67V67.5H87H23H22.5V67V25.6011V25.3941L22.6464 25.2476L35.2426 12.6465L35.3891 12.5H35.5962H87Z" stroke="#333333"/>
<path d="M34.6464 12.6464C34.7894 12.5034 35.0045 12.4607 35.1913 12.5381C35.3782 12.6155 35.5 12.7978 35.5 13V25C35.5 25.2761 35.2761 25.5 35 25.5H23C22.7978 25.5 22.6155 25.3782 22.5381 25.1913C22.4607 25.0045 22.5034 24.7894 22.6464 24.6464L34.6464 12.6464Z" fill="#ADADAD" stroke="#333333" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M79.4978 32.999C79.7739 32.999 79.9978 33.2229 79.9978 33.499C79.9978 33.7752 79.7739 33.999 79.4978 33.999H41.24C40.9638 33.999 40.74 33.7752 40.74 33.499C40.74 33.2229 40.9638 32.999 41.24 32.999H79.4978ZM78.6036 48.7217H79.1619C79.4381 48.7217 79.6619 48.4978 79.6619 48.2217C79.6619 47.9455 79.4381 47.7217 79.1619 47.7217H59.6974C59.5009 47.7217 59.3308 47.8351 59.2491 48H30.5008C30.2247 48 30.0008 48.2239 30.0008 48.5C30.0008 48.7761 30.2247 49 30.5008 49H78.1553C78.3519 49 78.5219 48.8866 78.6036 48.7217ZM79.9978 40.5C79.9978 40.2239 79.7739 40 79.4978 40H30.5009C30.2248 40 30.0009 40.2239 30.0009 40.5C30.0009 40.7761 30.2248 41 30.5009 41H79.4978C79.7739 41 79.9978 40.7761 79.9978 40.5Z" fill="#333333"/>
<path d="M68 24H46" stroke="#333333" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M46.9087 47.2391L40.8863 54.0228H6.06253C3.18567 54.0228 0.71251 56.6271 1.02706 58.7445L6.63442 93.1567C6.95553 95.2741 9.20001 97.5 12.0769 97.5H89.0957C91.9726 97.5 93.5497 95.6149 93.7135 93.4908L98.9674 48.6927C99.2296 47.3586 97.8927 45.5 95.5401 45.5H51.0045C49.4383 45.5 48.0293 46.0974 46.9087 47.2391Z" fill="#E07751" stroke="#333333"/>
<path d="M42.5 58.5V63.8974C42.5 64.6162 42.2088 65.2668 41.7378 65.7378C41.2668 66.2088 40.6162 66.5 39.8974 66.5H34.5" stroke="#333333"/>
<path d="M36.3051 88.3769L36.3051 88.3764L34.5169 66.0914L42.1422 58.5H63.3497L63.5022 58.5056C64.0348 58.5427 64.5355 58.7761 64.9034 59.1308C65.2702 59.4969 65.4955 59.9665 65.4999 60.4809L63.6757 88.3691C63.6757 88.3695 63.6756 88.3699 63.6756 88.3703C63.6334 88.97 63.3821 89.511 62.9988 89.8979C62.6237 90.2705 62.1155 90.5 61.5397 90.5H38.4415C37.8679 90.5 37.3704 90.272 36.9925 89.9017C36.6101 89.5207 36.3524 88.9726 36.3051 88.3769Z" stroke="#333333"/>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,16 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1041 57.7761L13.8689 12.0715C13.9289 10.5249 14.1552 9 14.1552 9L9.64338 9.0013C5.77243 9.0013 1.36612 12.6045 2.58695 18.4707L11.6311 55.1958C12.3458 58.5436 11.6029 63.1203 19.8212 62.9974C19.8808 62.9974 19.9403 62.9974 19.9999 62.9974C19.9403 62.9667 19.851 62.9667 19.7915 62.936C19.6128 62.9053 18.7493 62.721 18.5706 62.6903C16.3076 62.1374 14.1041 60.2946 14.1041 57.7761Z" fill="#ADADAD"/>
<path d="M19.6317 63.3769C19.6333 63.3765 19.6351 63.3761 19.6371 63.3756C19.6587 63.3705 19.6991 63.3629 19.7641 63.3541C20.0278 63.3184 20.5173 63.2856 21.2205 63.2574C22.6158 63.2014 24.7758 63.166 27.461 63.1467C32.8287 63.1081 40.2719 63.1338 47.8376 63.1853C59.7826 63.2667 72.0411 63.4124 76.8904 63.4701C78.1804 63.4854 78.9462 63.4945 79.0422 63.4945H79.4135L79.5209 63.139L79.0422 62.9945C79.5209 63.139 79.5209 63.1389 79.521 63.1387L79.5212 63.138L79.522 63.1353L79.5251 63.1248L79.5376 63.0833L79.5866 62.919C79.6299 62.7734 79.6942 62.556 79.7784 62.2679C79.947 61.6916 80.1955 60.8327 80.5156 59.7006C81.156 57.4363 82.0831 54.0793 83.2308 49.7055C85.5263 40.9579 88.7044 28.143 92.2366 11.869C92.7551 9.48018 92.5126 7.15211 91.4665 5.40004C90.4067 3.62493 88.5547 2.5 86.0104 2.5H21.1336C17.8294 2.5 14.2053 4.72796 14.2053 9.15304L14.2053 9.15573L14.2638 20.0278V20.0291V38.9427L13.9563 55.3903L13.9552 55.4495L13.9679 55.5072C14.1284 56.2357 14.0162 57.0492 13.8461 57.9348C13.8224 58.0586 13.7972 58.1847 13.7717 58.3124C13.6241 59.0521 13.4657 59.8455 13.5068 60.5542C13.5318 60.9855 13.63 61.4159 13.862 61.8108C14.0959 62.2089 14.4475 62.5401 14.9303 62.7983C15.8727 63.3023 17.3381 63.5406 19.5318 63.4944L19.6111 62.8715C19.688 62.8586 19.7876 62.8464 19.9089 62.8347C19.9169 62.8712 19.9211 62.9115 19.9197 62.9554C19.9125 63.1779 19.7668 63.296 19.7292 63.3239C19.6874 63.3548 19.6496 63.3707 19.6317 63.3769ZM19.6317 63.3769C19.6241 63.3789 19.6208 63.38 19.621 63.3801C19.6212 63.3802 19.6251 63.3793 19.6317 63.3769Z" fill="#333333"/>
<path d="M19.6317 63.3769C19.6333 63.3765 19.6351 63.3761 19.6371 63.3756C19.6587 63.3705 19.6991 63.3629 19.7641 63.3541C20.0278 63.3184 20.5173 63.2856 21.2205 63.2574C22.6158 63.2014 24.7758 63.166 27.461 63.1467C32.8287 63.1081 40.2719 63.1338 47.8376 63.1853C59.7826 63.2667 72.0411 63.4124 76.8904 63.4701C78.1804 63.4854 78.9462 63.4945 79.0422 63.4945H79.4135L79.5209 63.139L79.0422 62.9945C79.5209 63.139 79.5209 63.1389 79.521 63.1387L79.5212 63.138L79.522 63.1353L79.5251 63.1248L79.5376 63.0833L79.5866 62.919C79.6299 62.7734 79.6942 62.556 79.7784 62.2679C79.947 61.6916 80.1955 60.8327 80.5156 59.7006C81.156 57.4363 82.0831 54.0793 83.2308 49.7055C85.5263 40.9579 88.7044 28.143 92.2366 11.869C92.7551 9.48018 92.5126 7.15211 91.4665 5.40004C90.4067 3.62493 88.5547 2.5 86.0104 2.5H21.1336C17.8294 2.5 14.2053 4.72796 14.2053 9.15304L14.2053 9.15573L14.2638 20.0278V20.0291V38.9427L13.9563 55.3903L13.9552 55.4495L13.9679 55.5072C14.1284 56.2357 14.0162 57.0492 13.8461 57.9348C13.8224 58.0586 13.7972 58.1847 13.7717 58.3124C13.6241 59.0521 13.4657 59.8455 13.5068 60.5542C13.5318 60.9855 13.63 61.4159 13.862 61.8108C14.0959 62.2089 14.4475 62.5401 14.9303 62.7983C15.8727 63.3023 17.3381 63.5406 19.5318 63.4944L19.6111 62.8715C19.688 62.8586 19.7876 62.8464 19.9089 62.8347C19.9169 62.8712 19.9211 62.9115 19.9197 62.9554C19.9125 63.1779 19.7668 63.296 19.7292 63.3239C19.6874 63.3548 19.6496 63.3707 19.6317 63.3769ZM19.6317 63.3769C19.6241 63.3789 19.6208 63.38 19.621 63.3801C19.6212 63.3802 19.6251 63.3793 19.6317 63.3769Z" fill="white" fill-opacity="0.88"/>
<path d="M19.6317 63.3769C19.6333 63.3765 19.6351 63.3761 19.6371 63.3756C19.6587 63.3705 19.6991 63.3629 19.7641 63.3541C20.0278 63.3184 20.5173 63.2856 21.2205 63.2574C22.6158 63.2014 24.7758 63.166 27.461 63.1467C32.8287 63.1081 40.2719 63.1338 47.8376 63.1853C59.7826 63.2667 72.0411 63.4124 76.8904 63.4701C78.1804 63.4854 78.9462 63.4945 79.0422 63.4945H79.4135L79.5209 63.139L79.0422 62.9945C79.5209 63.139 79.5209 63.1389 79.521 63.1387L79.5212 63.138L79.522 63.1353L79.5251 63.1248L79.5376 63.0833L79.5866 62.919C79.6299 62.7734 79.6942 62.556 79.7784 62.2679C79.947 61.6916 80.1955 60.8327 80.5156 59.7006C81.156 57.4363 82.0831 54.0793 83.2308 49.7055C85.5263 40.9579 88.7044 28.143 92.2366 11.869C92.7551 9.48018 92.5126 7.15211 91.4665 5.40004C90.4067 3.62493 88.5547 2.5 86.0104 2.5H21.1336C17.8294 2.5 14.2053 4.72796 14.2053 9.15304L14.2053 9.15573L14.2638 20.0278V20.0291V38.9427L13.9563 55.3903L13.9552 55.4495L13.9679 55.5072C14.1284 56.2357 14.0162 57.0492 13.8461 57.9348C13.8224 58.0586 13.7972 58.1847 13.7717 58.3124C13.6241 59.0521 13.4657 59.8455 13.5068 60.5542C13.5318 60.9855 13.63 61.4159 13.862 61.8108C14.0959 62.2089 14.4475 62.5401 14.9303 62.7983C15.8727 63.3023 17.3381 63.5406 19.5318 63.4944L19.6111 62.8715C19.688 62.8586 19.7876 62.8464 19.9089 62.8347C19.9169 62.8712 19.9211 62.9115 19.9197 62.9554C19.9125 63.1779 19.7668 63.296 19.7292 63.3239C19.6874 63.3548 19.6496 63.3707 19.6317 63.3769ZM19.6317 63.3769C19.6241 63.3789 19.6208 63.38 19.621 63.3801C19.6212 63.3802 19.6251 63.3793 19.6317 63.3769Z" stroke="#333333"/>
<path d="M6.22947 35.0459L2.21578 18.2266C2.06374 17.5582 2.00293 16.8897 2.00293 16.2821C2.00293 14.0337 3.1841 10.954 6.22947 9.44702" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M42.5146 12.5156C42.5061 12.983 42.606 13.5378 42.8845 14.0437C43.3361 14.864 44.2253 15.5 45.6987 15.5H82.1474C83.4918 15.5 84.5605 15.111 85.371 14.4841C86.0712 13.9425 86.5585 13.2395 86.8691 12.5007L90.9046 12.5007L90.9104 12.5007C93.1746 12.4747 95.0087 13.1877 96.1524 14.4201C97.286 15.6418 97.8063 17.4439 97.3153 19.748L97.3152 19.748L97.3133 19.7577L90.3337 56.0214C90.3335 56.0225 90.3332 56.0237 90.333 56.0248C89.9655 57.7983 88.8461 59.4192 87.3374 60.6012C85.8277 61.7839 83.9623 62.5 82.1474 62.5H21.9194C20.1307 62.5 18.6409 61.7946 17.6827 60.6708C16.7265 59.5492 16.2718 57.9784 16.6125 56.186C16.6125 56.1857 16.6126 56.1854 16.6126 56.1851L23.997 18.1998L23.997 18.1998L23.9975 18.197C24.703 14.4524 27.9114 12.5156 31.7148 12.5156H42.5146Z" fill="#E07751" stroke="#333333"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.0981 31L31.626 31.0001C29.4923 31.0001 29.4237 34 31.6266 34H43.2983C45.5005 34 45.7007 31 43.0981 31Z" fill="#333333"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.0981 31L31.626 31.0001C29.4923 31.0001 29.4237 34 31.6266 34H43.2983C45.5005 34 45.7007 31 43.0981 31Z" fill="white" fill-opacity="0.88"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M52.0025 25L33.7078 25.0001C31.4668 25.0001 31.3947 28 33.7084 28H52.2127C54.5257 28 54.7359 25 52.0025 25Z" fill="#333333"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M52.0025 25L33.7078 25.0001C31.4668 25.0001 31.3947 28 33.7084 28H52.2127C54.5257 28 54.7359 25 52.0025 25Z" fill="white" fill-opacity="0.88"/>
<path d="M8.45151 41.5H91.5485C94.2483 41.5 96.5 43.7681 96.5 46.6095V91.3905C96.5 94.2039 94.2755 96.5 91.5485 96.5H8.45151C5.75166 96.5 3.5 94.2319 3.5 91.3905V46.612C3.5295 43.7633 5.75681 41.5 8.45151 41.5Z" fill="#333333"/>
<path d="M8.45151 41.5H91.5485C94.2483 41.5 96.5 43.7681 96.5 46.6095V91.3905C96.5 94.2039 94.2755 96.5 91.5485 96.5H8.45151C5.75166 96.5 3.5 94.2319 3.5 91.3905V46.612C3.5295 43.7633 5.75681 41.5 8.45151 41.5Z" fill="white" fill-opacity="0.88"/>
<path d="M8.45151 41.5H91.5485C94.2483 41.5 96.5 43.7681 96.5 46.6095V91.3905C96.5 94.2039 94.2755 96.5 91.5485 96.5H8.45151C5.75166 96.5 3.5 94.2319 3.5 91.3905V46.612C3.5295 43.7633 5.75681 41.5 8.45151 41.5Z" stroke="#333333"/>
<path d="M65.1182 62.5H35.8838C35.1109 62.5 34.4586 63.1239 34.502 63.9318V71.117C34.502 71.8553 35.0858 72.5 35.8838 72.5H65.1182C65.8572 72.5 66.5 71.9144 66.5 71.117V63.917C66.5 63.1572 65.9282 62.5 65.1182 62.5Z" fill="#E07751" stroke="#333333"/>
</svg>

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -0,0 +1,41 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M68 0.5H68.5V1V79V79.5H68H4H3.5V79V13.5528V13.3452L3.64706 13.1987L16.2433 0.645837L16.3896 0.5H16.5962H68Z" fill="#333333"/>
<path d="M68 0.5H68.5V1V79V79.5H68H4H3.5V79V13.5528V13.3452L3.64706 13.1987L16.2433 0.645837L16.3896 0.5H16.5962H68Z" fill="white" fill-opacity="0.88"/>
<path d="M68 0.5H68.5V1V79V79.5H68H4H3.5V79V13.5528V13.3452L3.64706 13.1987L16.2433 0.645837L16.3896 0.5H16.5962H68Z" stroke="#333333"/>
<path d="M15.6464 0.646447C15.7894 0.503448 16.0045 0.46067 16.1913 0.53806C16.3782 0.615451 16.5 0.797769 16.5 1V13C16.5 13.2761 16.2761 13.5 16 13.5H4C3.79777 13.5 3.61545 13.3782 3.53806 13.1913C3.46067 13.0045 3.50345 12.7894 3.64645 12.6464L15.6464 0.646447Z" fill="#ADADAD" stroke="#333333" stroke-linejoin="round"/>
<path d="M77 7.5H77.5V8V86V86.5H77H13H12.5V86V20.5528V20.3452L12.6471 20.1987L25.2433 7.64584L25.3896 7.5H25.5962H77Z" fill="#333333"/>
<path d="M77 7.5H77.5V8V86V86.5H77H13H12.5V86V20.5528V20.3452L12.6471 20.1987L25.2433 7.64584L25.3896 7.5H25.5962H77Z" fill="white" fill-opacity="0.88"/>
<path d="M77 7.5H77.5V8V86V86.5H77H13H12.5V86V20.5528V20.3452L12.6471 20.1987L25.2433 7.64584L25.3896 7.5H25.5962H77Z" stroke="#333333"/>
<path d="M24.6464 7.64645C24.7894 7.50345 25.0045 7.46067 25.1913 7.53806C25.3782 7.61545 25.5 7.79777 25.5 8V20C25.5 20.2761 25.2761 20.5 25 20.5H13C12.7978 20.5 12.6155 20.3782 12.5381 20.1913C12.4607 20.0045 12.5034 19.7894 12.6464 19.6464L24.6464 7.64645Z" fill="#ADADAD" stroke="#333333" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M85 15H34.3994L22 27.286V92H85V15Z" fill="#333333"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M85 15H34.3994L22 27.286V92H85V15Z" fill="white" fill-opacity="0.88"/>
<path d="M85.5 45.9913V14.5H34.0962L21.5 26.9456V92.5H50.5" stroke="#333333" stroke-linecap="round"/>
<path d="M33.6464 14.6464C33.7894 14.5034 34.0045 14.4607 34.1913 14.5381C34.3782 14.6155 34.5 14.7978 34.5 15V27C34.5 27.2761 34.2761 27.5 34 27.5H22C21.7978 27.5 21.6154 27.3782 21.5381 27.1913C21.4607 27.0045 21.5034 26.7894 21.6464 26.6464L33.6464 14.6464Z" fill="#E07751" stroke="#333333" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M75.5 32.9982C75.7761 32.9982 76 33.222 76 33.4982C76 33.7743 75.7761 33.9982 75.5 33.9982H42.5C42.2238 33.9982 42 33.7743 42 33.4982C42 33.222 42.2238 32.9982 42.5 32.9982H75.5ZM47.4914 68.9982C47.7675 68.9982 47.9914 69.222 47.9914 69.4982C47.9914 69.7743 47.7675 69.9982 47.4914 69.9982H32.2739C31.9978 69.9982 31.7739 69.7743 31.7739 69.4982C31.7739 69.222 31.9978 68.9982 32.2739 68.9982H47.4914ZM56.7706 55.4982C56.7706 55.222 56.5468 54.9982 56.2706 54.9982H32.2739C31.9978 54.9982 31.7739 55.222 31.7739 55.4982C31.7739 55.7743 31.9978 55.9982 32.2739 55.9982H56.2706C56.5468 55.9982 56.7706 55.7743 56.7706 55.4982ZM75.5 39.9982C75.7761 39.9982 76 40.222 76 40.4982C76 40.7743 75.7761 40.9982 75.5 40.9982H32.2739C31.9978 40.9982 31.7739 40.7743 31.7739 40.4982C31.7739 40.222 31.9978 39.9982 32.2739 39.9982H75.5ZM50.3325 62.4982C50.3325 62.222 50.1087 61.9982 49.8325 61.9982H32.2739C31.9978 61.9982 31.7739 62.222 31.7739 62.4982C31.7739 62.7743 31.9978 62.9982 32.2739 62.9982H49.8325C50.1087 62.9982 50.3325 62.7743 50.3325 62.4982ZM46.9061 75.9982C47.1822 75.9982 47.4061 76.222 47.4061 76.4982C47.4061 76.7743 47.1822 76.9982 46.9061 76.9982H32.2739C31.9978 76.9982 31.7739 76.7743 31.7739 76.4982C31.7739 76.222 31.9978 75.9982 32.2739 75.9982H46.9061ZM75.5 47C75.7761 47 76 47.2239 76 47.5C76 47.7761 75.7761 48 75.5 48H58.5267C58.2506 48 58.0267 47.7761 58.0267 47.5C58.0267 47.2239 58.2506 47 58.5267 47H75.5ZM53.8442 47C54.1204 47 54.3442 47.2239 54.3442 47.5C54.3442 47.7761 54.1204 48 53.8442 48H32.7739C32.4978 48 32.2739 47.7761 32.2739 47.5C32.2739 47.2239 32.4978 47 32.7739 47H53.8442ZM75.2075 70.1947C75.4836 70.1947 75.7075 70.4185 75.7075 70.6947C75.7075 70.9708 75.4836 71.1947 75.2075 71.1947H58.2342C57.9581 71.1947 57.7342 70.9708 57.7342 70.6947C57.7342 70.4185 57.9581 70.1947 58.2342 70.1947H75.2075Z" fill="#333333"/>
<path d="M73 25.9999H51" stroke="#E07751" stroke-width="2" stroke-linecap="round"/>
<path d="M76 99.5C88.9401 99.5 99.5 88.9472 99.5 76C99.5 63.0529 88.9471 52.5 76 52.5C63.0529 52.5 52.5 63.0529 52.5 76C52.5 88.9471 63.0529 99.5 76 99.5Z" fill="#ADADAD" stroke="#333333"/>
<path d="M69.175 76.7831L69.1771 76.7808L69.2813 76.6659C69.3326 76.6099 69.4055 76.518 69.4409 76.3761C69.4645 76.2814 69.4644 76.1837 69.4643 76.131C69.4643 76.1282 69.4643 76.1256 69.4643 76.1232V75.731V73.3198V72.5393L68.7553 72.8656C68.2133 73.1151 67.7192 73.1212 66.9999 73.1212C66.3021 73.1212 65.7222 73.071 65.233 72.8605L64.5354 72.5604V73.3198V75.7456V76.1232C64.5354 76.1269 64.5354 76.1312 64.5354 76.1361C64.5351 76.1818 64.5346 76.2727 64.5514 76.355C64.5811 76.5006 64.6517 76.5987 64.7131 76.6659L64.8174 76.7808L64.8174 76.7808L64.8195 76.7831C65.0564 77.0412 65.3072 77.2805 65.5484 77.5107C65.5613 77.523 65.5741 77.5353 65.587 77.5476C65.8437 77.7927 66.0913 78.031 66.3296 78.2945L66.3304 78.2954C66.3951 78.3666 66.4836 78.4585 66.5912 78.5272C66.696 78.594 66.8357 78.6491 66.9999 78.6415C67.1641 78.6491 67.3037 78.594 67.4086 78.5272C67.5162 78.4585 67.6047 78.3666 67.6693 78.2954L67.2993 77.9592L67.6688 78.296C67.9084 78.033 68.1559 77.7938 68.4115 77.5479C68.4209 77.5388 68.4304 77.5296 68.44 77.5205C68.6833 77.2864 68.9356 77.0438 69.175 76.7831Z" fill="#333333"/>
<path d="M69.175 76.7831L69.1771 76.7808L69.2813 76.6659C69.3326 76.6099 69.4055 76.518 69.4409 76.3761C69.4645 76.2814 69.4644 76.1837 69.4643 76.131C69.4643 76.1282 69.4643 76.1256 69.4643 76.1232V75.731V73.3198V72.5393L68.7553 72.8656C68.2133 73.1151 67.7192 73.1212 66.9999 73.1212C66.3021 73.1212 65.7222 73.071 65.233 72.8605L64.5354 72.5604V73.3198V75.7456V76.1232C64.5354 76.1269 64.5354 76.1312 64.5354 76.1361C64.5351 76.1818 64.5346 76.2727 64.5514 76.355C64.5811 76.5006 64.6517 76.5987 64.7131 76.6659L64.8174 76.7808L64.8174 76.7808L64.8195 76.7831C65.0564 77.0412 65.3072 77.2805 65.5484 77.5107C65.5613 77.523 65.5741 77.5353 65.587 77.5476C65.8437 77.7927 66.0913 78.031 66.3296 78.2945L66.3304 78.2954C66.3951 78.3666 66.4836 78.4585 66.5912 78.5272C66.696 78.594 66.8357 78.6491 66.9999 78.6415C67.1641 78.6491 67.3037 78.594 67.4086 78.5272C67.5162 78.4585 67.6047 78.3666 67.6693 78.2954L67.2993 77.9592L67.6688 78.296C67.9084 78.033 68.1559 77.7938 68.4115 77.5479C68.4209 77.5388 68.4304 77.5296 68.44 77.5205C68.6833 77.2864 68.9356 77.0438 69.175 76.7831Z" fill="white" fill-opacity="0.88"/>
<path d="M69.175 76.7831L69.1771 76.7808L69.2813 76.6659C69.3326 76.6099 69.4055 76.518 69.4409 76.3761C69.4645 76.2814 69.4644 76.1837 69.4643 76.131C69.4643 76.1282 69.4643 76.1256 69.4643 76.1232V75.731V73.3198V72.5393L68.7553 72.8656C68.2133 73.1151 67.7192 73.1212 66.9999 73.1212C66.3021 73.1212 65.7222 73.071 65.233 72.8605L64.5354 72.5604V73.3198V75.7456V76.1232C64.5354 76.1269 64.5354 76.1312 64.5354 76.1361C64.5351 76.1818 64.5346 76.2727 64.5514 76.355C64.5811 76.5006 64.6517 76.5987 64.7131 76.6659L64.8174 76.7808L64.8174 76.7808L64.8195 76.7831C65.0564 77.0412 65.3072 77.2805 65.5484 77.5107C65.5613 77.523 65.5741 77.5353 65.587 77.5476C65.8437 77.7927 66.0913 78.031 66.3296 78.2945L66.3304 78.2954C66.3951 78.3666 66.4836 78.4585 66.5912 78.5272C66.696 78.594 66.8357 78.6491 66.9999 78.6415C67.1641 78.6491 67.3037 78.594 67.4086 78.5272C67.5162 78.4585 67.6047 78.3666 67.6693 78.2954L67.2993 77.9592L67.6688 78.296C67.9084 78.033 68.1559 77.7938 68.4115 77.5479C68.4209 77.5388 68.4304 77.5296 68.44 77.5205C68.6833 77.2864 68.9356 77.0438 69.175 76.7831Z" stroke="#333333"/>
<ellipse cx="66.9924" cy="68.2973" rx="5.39495" ry="5.29728" fill="#333333"/>
<ellipse cx="66.9924" cy="68.2973" rx="5.39495" ry="5.29728" fill="white" fill-opacity="0.88"/>
<ellipse cx="66.9924" cy="68.2973" rx="5.39495" ry="5.29728" stroke="#333333"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M67.2639 65.3095C68.2833 66.9933 70.6862 68.9415 72.361 68.7093C72.3809 68.5416 72.3875 68.3738 72.3875 68.1996C72.3875 65.3289 69.9978 63 67.052 63C64.1063 63 61.7166 65.3289 61.7166 68.1996C61.7166 68.3996 61.7298 68.5932 61.7496 68.7867C63.3582 68.7674 65.9929 67.7158 67.2639 65.3095Z" fill="#ADADAD" stroke="#333333"/>
<path d="M70.0265 75.7157L69.7797 75.6913L69.6109 75.8731L67.3974 78.2585C67.2005 78.4706 66.8652 78.4718 66.6668 78.261L64.4172 75.8706L64.2519 75.695L64.0116 75.715C62.0667 75.8768 59.9229 76.3683 58.2688 77.3574L57.7813 77.649L58.1323 78.0955C60.2445 80.7826 63.4471 82.5 67.0432 82.5C70.6172 82.5 73.8047 80.7982 75.9167 78.1424L76.2771 77.6892L75.7762 77.3987C74.6567 76.7494 72.6868 75.9782 70.0265 75.7157Z" fill="#ADADAD" stroke="#333333"/>
<path d="M87.1496 76.7831L87.1517 76.7808L87.2559 76.6659C87.3072 76.6099 87.3801 76.518 87.4155 76.3761C87.4391 76.2814 87.439 76.1837 87.4389 76.131C87.4389 76.1282 87.4389 76.1256 87.4389 76.1232V75.731V73.3198V72.5393L86.7299 72.8656C86.1879 73.1151 85.6938 73.1212 84.9745 73.1212C84.2767 73.1212 83.6968 73.071 83.2076 72.8605L82.51 72.5604V73.3198V75.7456V76.1232C82.51 76.1269 82.51 76.1312 82.51 76.1361C82.5097 76.1818 82.5092 76.2727 82.526 76.355C82.5557 76.5006 82.6263 76.5987 82.6878 76.6659L82.792 76.7808L82.792 76.7808L82.7941 76.7831C83.0311 77.0412 83.2819 77.2805 83.523 77.5107C83.5359 77.523 83.5487 77.5353 83.5616 77.5476C83.8183 77.7927 84.0659 78.031 84.3042 78.2945L84.305 78.2954C84.3697 78.3666 84.4582 78.4585 84.5658 78.5272C84.6706 78.594 84.8103 78.6491 84.9745 78.6415C85.1387 78.6491 85.2783 78.594 85.3832 78.5272C85.4908 78.4585 85.5793 78.3666 85.644 78.2954L85.2739 77.9592L85.6435 78.296C85.883 78.033 86.1305 77.7938 86.3861 77.5479C86.3956 77.5388 86.4051 77.5296 86.4146 77.5205C86.6579 77.2864 86.9102 77.0438 87.1496 76.7831Z" fill="#333333"/>
<path d="M87.1496 76.7831L87.1517 76.7808L87.2559 76.6659C87.3072 76.6099 87.3801 76.518 87.4155 76.3761C87.4391 76.2814 87.439 76.1837 87.4389 76.131C87.4389 76.1282 87.4389 76.1256 87.4389 76.1232V75.731V73.3198V72.5393L86.7299 72.8656C86.1879 73.1151 85.6938 73.1212 84.9745 73.1212C84.2767 73.1212 83.6968 73.071 83.2076 72.8605L82.51 72.5604V73.3198V75.7456V76.1232C82.51 76.1269 82.51 76.1312 82.51 76.1361C82.5097 76.1818 82.5092 76.2727 82.526 76.355C82.5557 76.5006 82.6263 76.5987 82.6878 76.6659L82.792 76.7808L82.792 76.7808L82.7941 76.7831C83.0311 77.0412 83.2819 77.2805 83.523 77.5107C83.5359 77.523 83.5487 77.5353 83.5616 77.5476C83.8183 77.7927 84.0659 78.031 84.3042 78.2945L84.305 78.2954C84.3697 78.3666 84.4582 78.4585 84.5658 78.5272C84.6706 78.594 84.8103 78.6491 84.9745 78.6415C85.1387 78.6491 85.2783 78.594 85.3832 78.5272C85.4908 78.4585 85.5793 78.3666 85.644 78.2954L85.2739 77.9592L85.6435 78.296C85.883 78.033 86.1305 77.7938 86.3861 77.5479C86.3956 77.5388 86.4051 77.5296 86.4146 77.5205C86.6579 77.2864 86.9102 77.0438 87.1496 76.7831Z" fill="white" fill-opacity="0.88"/>
<path d="M87.1496 76.7831L87.1517 76.7808L87.2559 76.6659C87.3072 76.6099 87.3801 76.518 87.4155 76.3761C87.4391 76.2814 87.439 76.1837 87.4389 76.131C87.4389 76.1282 87.4389 76.1256 87.4389 76.1232V75.731V73.3198V72.5393L86.7299 72.8656C86.1879 73.1151 85.6938 73.1212 84.9745 73.1212C84.2767 73.1212 83.6968 73.071 83.2076 72.8605L82.51 72.5604V73.3198V75.7456V76.1232C82.51 76.1269 82.51 76.1312 82.51 76.1361C82.5097 76.1818 82.5092 76.2727 82.526 76.355C82.5557 76.5006 82.6263 76.5987 82.6878 76.6659L82.792 76.7808L82.792 76.7808L82.7941 76.7831C83.0311 77.0412 83.2819 77.2805 83.523 77.5107C83.5359 77.523 83.5487 77.5353 83.5616 77.5476C83.8183 77.7927 84.0659 78.031 84.3042 78.2945L84.305 78.2954C84.3697 78.3666 84.4582 78.4585 84.5658 78.5272C84.6706 78.594 84.8103 78.6491 84.9745 78.6415C85.1387 78.6491 85.2783 78.594 85.3832 78.5272C85.4908 78.4585 85.5793 78.3666 85.644 78.2954L85.2739 77.9592L85.6435 78.296C85.883 78.033 86.1305 77.7938 86.3861 77.5479C86.3956 77.5388 86.4051 77.5296 86.4146 77.5205C86.6579 77.2864 86.9102 77.0438 87.1496 76.7831Z" stroke="#333333"/>
<ellipse cx="84.967" cy="68.2973" rx="5.39495" ry="5.29728" fill="#333333"/>
<ellipse cx="84.967" cy="68.2973" rx="5.39495" ry="5.29728" fill="white" fill-opacity="0.88"/>
<ellipse cx="84.967" cy="68.2973" rx="5.39495" ry="5.29728" stroke="#333333"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M85.2385 65.3095C86.2579 66.9933 88.6609 68.9415 90.3356 68.7093C90.3555 68.5416 90.3621 68.3738 90.3621 68.1996C90.3621 65.3289 87.9724 63 85.0266 63C82.0809 63 79.6912 65.3289 79.6912 68.1996C79.6912 68.3996 79.7044 68.5932 79.7243 68.7867C81.3328 68.7674 83.9675 67.7158 85.2385 65.3095Z" fill="#ADADAD" stroke="#333333"/>
<path d="M88.0011 75.7157L87.7543 75.6913L87.5855 75.8731L85.372 78.2585C85.1751 78.4706 84.8398 78.4718 84.6414 78.261L82.3918 75.8706L82.2265 75.695L81.9862 75.715C80.0413 75.8768 77.8975 76.3683 76.2434 77.3574L75.7559 77.649L76.1069 78.0955C78.2191 80.7826 81.4218 82.5 85.0178 82.5C88.5918 82.5 91.7793 80.7982 93.8913 78.1424L94.2518 77.6892L93.7509 77.3987C92.6314 76.7494 90.6614 75.9782 88.0011 75.7157Z" fill="#ADADAD" stroke="#333333"/>
<path d="M78.1496 86.2831L78.1517 86.2808L78.2559 86.1659C78.3072 86.1099 78.3801 86.018 78.4155 85.8761C78.4391 85.7814 78.439 85.6837 78.4389 85.631C78.4389 85.6282 78.4389 85.6256 78.4389 85.6232V85.231V82.8198V82.0393L77.7299 82.3656C77.1879 82.6151 76.6938 82.6212 75.9745 82.6212C75.2767 82.6212 74.6968 82.571 74.2076 82.3605L73.51 82.0604V82.8198V85.2456V85.6232C73.51 85.6269 73.51 85.6312 73.51 85.6361C73.5097 85.6818 73.5092 85.7727 73.526 85.855C73.5557 86.0006 73.6263 86.0987 73.6878 86.1659L73.792 86.2808L73.792 86.2808L73.7941 86.2831C74.0311 86.5412 74.2819 86.7805 74.523 87.0107C74.5359 87.023 74.5487 87.0353 74.5616 87.0476C74.8183 87.2927 75.0659 87.531 75.3042 87.7945L75.305 87.7954C75.3697 87.8666 75.4582 87.9585 75.5658 88.0272C75.6706 88.094 75.8103 88.1491 75.9745 88.1415C76.1387 88.1491 76.2783 88.094 76.3832 88.0272C76.4908 87.9585 76.5793 87.8666 76.644 87.7954L76.2739 87.4592L76.6435 87.796C76.883 87.533 77.1305 87.2938 77.3861 87.0479C77.3956 87.0388 77.4051 87.0296 77.4146 87.0205C77.6579 86.7864 77.9102 86.5438 78.1496 86.2831Z" fill="#333333"/>
<path d="M78.1496 86.2831L78.1517 86.2808L78.2559 86.1659C78.3072 86.1099 78.3801 86.018 78.4155 85.8761C78.4391 85.7814 78.439 85.6837 78.4389 85.631C78.4389 85.6282 78.4389 85.6256 78.4389 85.6232V85.231V82.8198V82.0393L77.7299 82.3656C77.1879 82.6151 76.6938 82.6212 75.9745 82.6212C75.2767 82.6212 74.6968 82.571 74.2076 82.3605L73.51 82.0604V82.8198V85.2456V85.6232C73.51 85.6269 73.51 85.6312 73.51 85.6361C73.5097 85.6818 73.5092 85.7727 73.526 85.855C73.5557 86.0006 73.6263 86.0987 73.6878 86.1659L73.792 86.2808L73.792 86.2808L73.7941 86.2831C74.0311 86.5412 74.2819 86.7805 74.523 87.0107C74.5359 87.023 74.5487 87.0353 74.5616 87.0476C74.8183 87.2927 75.0659 87.531 75.3042 87.7945L75.305 87.7954C75.3697 87.8666 75.4582 87.9585 75.5658 88.0272C75.6706 88.094 75.8103 88.1491 75.9745 88.1415C76.1387 88.1491 76.2783 88.094 76.3832 88.0272C76.4908 87.9585 76.5793 87.8666 76.644 87.7954L76.2739 87.4592L76.6435 87.796C76.883 87.533 77.1305 87.2938 77.3861 87.0479C77.3956 87.0388 77.4051 87.0296 77.4146 87.0205C77.6579 86.7864 77.9102 86.5438 78.1496 86.2831Z" fill="white" fill-opacity="0.88"/>
<path d="M78.1496 86.2831L78.1517 86.2808L78.2559 86.1659C78.3072 86.1099 78.3801 86.018 78.4155 85.8761C78.4391 85.7814 78.439 85.6837 78.4389 85.631C78.4389 85.6282 78.4389 85.6256 78.4389 85.6232V85.231V82.8198V82.0393L77.7299 82.3656C77.1879 82.6151 76.6938 82.6212 75.9745 82.6212C75.2767 82.6212 74.6968 82.571 74.2076 82.3605L73.51 82.0604V82.8198V85.2456V85.6232C73.51 85.6269 73.51 85.6312 73.51 85.6361C73.5097 85.6818 73.5092 85.7727 73.526 85.855C73.5557 86.0006 73.6263 86.0987 73.6878 86.1659L73.792 86.2808L73.792 86.2808L73.7941 86.2831C74.0311 86.5412 74.2819 86.7805 74.523 87.0107C74.5359 87.023 74.5487 87.0353 74.5616 87.0476C74.8183 87.2927 75.0659 87.531 75.3042 87.7945L75.305 87.7954C75.3697 87.8666 75.4582 87.9585 75.5658 88.0272C75.6706 88.094 75.8103 88.1491 75.9745 88.1415C76.1387 88.1491 76.2783 88.094 76.3832 88.0272C76.4908 87.9585 76.5793 87.8666 76.644 87.7954L76.2739 87.4592L76.6435 87.796C76.883 87.533 77.1305 87.2938 77.3861 87.0479C77.3956 87.0388 77.4051 87.0296 77.4146 87.0205C77.6579 86.7864 77.9102 86.5438 78.1496 86.2831Z" stroke="#333333"/>
<ellipse cx="75.967" cy="77.7973" rx="5.39495" ry="5.29728" fill="#333333"/>
<ellipse cx="75.967" cy="77.7973" rx="5.39495" ry="5.29728" fill="white" fill-opacity="0.88"/>
<ellipse cx="75.967" cy="77.7973" rx="5.39495" ry="5.29728" stroke="#333333"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M76.2385 74.8095C77.2579 76.4933 79.6609 78.4415 81.3356 78.2093C81.3555 78.0416 81.3621 77.8738 81.3621 77.6996C81.3621 74.8289 78.9724 72.5 76.0266 72.5C73.0809 72.5 70.6912 74.8289 70.6912 77.6996C70.6912 77.8996 70.7044 78.0932 70.7243 78.2867C72.3328 78.2674 74.9675 77.2158 76.2385 74.8095Z" fill="#ADADAD" stroke="#333333"/>
<path d="M79.0011 85.2157L78.7543 85.1913L78.5855 85.3731L76.372 87.7585C76.1751 87.9706 75.8398 87.9718 75.6414 87.761L73.3918 85.3706L73.2265 85.195L72.9862 85.215C71.0413 85.3768 68.8975 85.8683 67.2434 86.8574L66.7559 87.149L67.1069 87.5955C69.2191 90.2826 72.4218 92 76.0178 92C79.5918 92 82.7793 90.2982 84.8913 87.6424L85.2518 87.1892L84.7509 86.8987C83.6314 86.2494 81.6614 85.4782 79.0011 85.2157Z" fill="#E07751" stroke="#333333"/>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

Some files were not shown because too many files have changed in this diff Show More