Compare commits

...

5 Commits

Author SHA1 Message Date
2248356998 qq.com
694437c7d5 更新readme 2025-10-30 18:06:54 +08:00
2248356998 qq.com
0e78cdefe7 feat: 支持net10 sdk下正确编译 net462 2025-10-30 17:57:17 +08:00
2248356998 qq.com
087dc9aaa3 更新logo 2025-10-30 17:20:12 +08:00
2248356998 qq.com
dacf255f1a fix: mqttCollect插件无法订阅两个以上的主题 2025-10-28 23:34:47 +08:00
2248356998 qq.com
e3960ce115 feat(DeviceBase): 发送前检查在线状态 2025-10-28 20:00:24 +08:00
41 changed files with 220 additions and 180 deletions

View File

@@ -1,4 +1,7 @@
# ThingsGateway
<p align="center">
<img src="logo.svg" width = "400" height = "200" alt="The name of the image" align=center />
</p>
[![star](https://gitee.com/ThingsGateway/ThingsGateway/badge/star.svg?theme=gvp)](https://gitee.com/ThingsGateway/ThingsGateway/stargazers)
[![star](https://img.shields.io/github/stars/ThingsGateway/ThingsGateway?logo=github)](https://github.com/ThingsGateway/ThingsGateway)
@@ -11,31 +14,31 @@
</a>
## Introduction

A cross-platform, high-performance edge data collection gateway based on net8/10.

## Documentation

[Documentation](https://thingsgateway.cn/).

[NuGet](https://www.nuget.org/packages?q=Tags%3A%22ThingsGateway%22)

## Demo

[Demo](https://demo.thingsgateway.cn/)

Account: **SuperAdmin**

Password: **111111**

## Docker
@@ -50,7 +53,7 @@ docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64
### Plugin List

#### Data Collection Plugins
@@ -80,28 +83,28 @@ docker pull registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64
| TDengineDB | Time-series database storage |
| QuestDB | Time-series database storage |

## License

[License](https://thingsgateway.cn/docs/1)


## Sponsorship

[Sponsorship Approach](https://thingsgateway.cn/docs/1000)

## Community

QQ Group: 605534569 [Jump](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=NnBjPO-8kcNFzo_RzSbdICflb97u2O1i&authKey=V1MI3iJtpDMHc08myszP262kDykbx2Yev6ebE4Me0elTe0P0IFAmtU5l7Sy5w0jx&noverify=0&group_code=605534569)

## Pro Plugins

[Plugin List](https://thingsgateway.cn/docs/1001)

View File

@@ -1,4 +1,7 @@
# ThingsGateway
<p align="center">
<img src="logo.svg" width = "400" height = "200" alt="The name of the image" align=center />
</p>
[![star](https://gitee.com/ThingsGateway/ThingsGateway/badge/star.svg?theme=gvp)](https://gitee.com/ThingsGateway/ThingsGateway/stargazers)
[![star](https://img.shields.io/github/stars/ThingsGateway/ThingsGateway?logo=github)](https://github.com/ThingsGateway/ThingsGateway)

BIN
icon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 177 KiB

9
logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -0,0 +1,134 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Common;
/// <inheritdoc/>
[ThingsGateway.DependencyInjection.SuppressSniffer]
public static class EnumerableQueryPageOptionsExtensions
{
public static IEnumerable<T> GetData<T>(this IEnumerable<T> datas, QueryPageOptions option, out int totalCount, FilterKeyValueAction where = null)
{
totalCount = 0;
if (datas == null)
return new List<T>();
where ??= option.ToFilter();
if (where.HasFilters())
{
datas = datas.Where(where.GetFilterFunc<T>());//name asc模式
}
if (option.SortList.Count > 0)
{
datas = datas.Sort(option.SortList);//name asc模式
}
if (option.AdvancedSortList.Count > 0)
{
datas = datas.Sort(option.AdvancedSortList);//name asc模式
}
if (option.SortOrder != SortOrder.Unset && !option.SortName.IsNullOrWhiteSpace())
{
datas = datas.Sort(option.SortName, option.SortOrder);
}
totalCount = datas.Count();
if (option.IsPage)
{
datas = datas.Skip((option.PageIndex - 1) * option.PageItems).Take(option.PageItems);
}
else if (option.IsVirtualScroll)
{
datas = datas.Skip((option.StartIndex) * option.PageItems).Take(option.PageItems);
}
return datas;
}
public static IEnumerable<T> GetQuery<T>(this IEnumerable<T> query, QueryPageOptions option, Func<IEnumerable<T>, IEnumerable<T>>? queryFunc = null, FilterKeyValueAction where = null)
{
if (queryFunc != null)
query = queryFunc(query);
where ??= option.ToFilter();
if (where.HasFilters())
{
query = query.Where(where.GetFilterFunc<T>());//name asc模式
}
if (option.SortOrder != SortOrder.Unset && !string.IsNullOrEmpty(option.SortName))
{
var invoker = Utility.GetSortFunc<T>();
query = invoker(query, option.SortName, option.SortOrder);
}
else if (option.SortList.Count > 0)
{
var invoker = Utility.GetSortListFunc<T>();
query = invoker(query, option.SortList);
}
else if (option.AdvancedSortList.Count > 0)
{
var invoker = Utility.GetSortListFunc<T>();
query = invoker(query, option.AdvancedSortList);
}
return query;
}
/// <summary>
/// 根据查询条件返回QueryData
/// </summary>
public static QueryData<T> GetQueryData<T>(this IEnumerable<T> datas, QueryPageOptions option, FilterKeyValueAction where = null)
{
var ret = new QueryData<T>()
{
IsSorted = option.SortOrder != SortOrder.Unset,
IsFiltered = option.Filters.Count > 0,
IsAdvanceSearch = option.AdvanceSearches.Count > 0 || option.CustomerSearches.Count > 0,
IsSearch = option.Searches.Count > 0
};
var items = datas.GetData(option, out var totalCount, where);
ret.TotalCount = totalCount;
if (totalCount > 0)
{
if (!items.Any() && option.PageIndex != 1)
{
option.PageIndex = 1;
items = datas.GetData(option, out totalCount, where);
}
}
ret.Items = items.ToList();
return ret;
}
/// <summary>
/// 根据查询条件返回QueryData
/// </summary>
public static QueryData<SelectedItem> GetQueryData<T>(this IEnumerable<T> datas, VirtualizeQueryOption option, Func<IEnumerable<T>, IEnumerable<SelectedItem>> func, FilterKeyValueAction where = null)
{
var ret = new QueryData<SelectedItem>()
{
IsSorted = false,
IsFiltered = false,
IsAdvanceSearch = false,
IsSearch = !option.SearchText.IsNullOrWhiteSpace()
};
var items = datas.Skip((option.StartIndex)).Take(option.Count);
ret.TotalCount = datas.Count();
ret.Items = func(items).ToList();
return ret;
}
}

View File

@@ -19,7 +19,7 @@ using System.Reflection;
using ThingsGateway.Common.Extension;
namespace ThingsGateway.DB;
namespace ThingsGateway.Common;
/// <summary>
/// 导出excel扩展

View File

@@ -11,7 +11,7 @@
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Http;
namespace ThingsGateway.DB;
namespace ThingsGateway.Common;
/// <inheritdoc/>
[ThingsGateway.DependencyInjection.SuppressSniffer]

View File

@@ -10,7 +10,7 @@
using Yitter.IdGenerator;
namespace ThingsGateway.DB;
namespace ThingsGateway.Common;
/// <summary>
/// 公共功能

View File

@@ -16,74 +16,6 @@ namespace ThingsGateway.DB;
[ThingsGateway.DependencyInjection.SuppressSniffer]
public static class QueryPageOptionsExtensions
{
public static IEnumerable<T> GetData<T>(this IEnumerable<T> datas, QueryPageOptions option, out int totalCount, FilterKeyValueAction where = null)
{
totalCount = 0;
if (datas == null)
return new List<T>();
where ??= option.ToFilter();
if (where.HasFilters())
{
datas = datas.Where(where.GetFilterFunc<T>());//name asc模式
}
if (option.SortList.Count > 0)
{
datas = datas.Sort(option.SortList);//name asc模式
}
if (option.AdvancedSortList.Count > 0)
{
datas = datas.Sort(option.AdvancedSortList);//name asc模式
}
if (option.SortOrder != SortOrder.Unset && !option.SortName.IsNullOrWhiteSpace())
{
datas = datas.Sort(option.SortName, option.SortOrder);
}
totalCount = datas.Count();
if (option.IsPage)
{
datas = datas.Skip((option.PageIndex - 1) * option.PageItems).Take(option.PageItems);
}
else if (option.IsVirtualScroll)
{
datas = datas.Skip((option.StartIndex) * option.PageItems).Take(option.PageItems);
}
return datas;
}
public static IEnumerable<T> GetQuery<T>(this IEnumerable<T> query, QueryPageOptions option, Func<IEnumerable<T>, IEnumerable<T>>? queryFunc = null, FilterKeyValueAction where = null)
{
if (queryFunc != null)
query = queryFunc(query);
where ??= option.ToFilter();
if (where.HasFilters())
{
query = query.Where(where.GetFilterFunc<T>());//name asc模式
}
if (option.SortOrder != SortOrder.Unset && !string.IsNullOrEmpty(option.SortName))
{
var invoker = Utility.GetSortFunc<T>();
query = invoker(query, option.SortName, option.SortOrder);
}
else if (option.SortList.Count > 0)
{
var invoker = Utility.GetSortListFunc<T>();
query = invoker(query, option.SortList);
}
else if (option.AdvancedSortList.Count > 0)
{
var invoker = Utility.GetSortListFunc<T>();
query = invoker(query, option.AdvancedSortList);
}
return query;
}
/// <summary>
/// 根据查询条件返回sqlsugar ISugarQueryable
/// </summary>
@@ -111,50 +43,4 @@ public static class QueryPageOptionsExtensions
return query;
}
/// <summary>
/// 根据查询条件返回QueryData
/// </summary>
public static QueryData<T> GetQueryData<T>(this IEnumerable<T> datas, QueryPageOptions option, FilterKeyValueAction where = null)
{
var ret = new QueryData<T>()
{
IsSorted = option.SortOrder != SortOrder.Unset,
IsFiltered = option.Filters.Count > 0,
IsAdvanceSearch = option.AdvanceSearches.Count > 0 || option.CustomerSearches.Count > 0,
IsSearch = option.Searches.Count > 0
};
var items = datas.GetData(option, out var totalCount, where);
ret.TotalCount = totalCount;
if (totalCount > 0)
{
if (!items.Any() && option.PageIndex != 1)
{
option.PageIndex = 1;
items = datas.GetData(option, out totalCount, where);
}
}
ret.Items = items.ToList();
return ret;
}
/// <summary>
/// 根据查询条件返回QueryData
/// </summary>
public static QueryData<SelectedItem> GetQueryData<T>(this IEnumerable<T> datas, VirtualizeQueryOption option, Func<IEnumerable<T>, IEnumerable<SelectedItem>> func, FilterKeyValueAction where = null)
{
var ret = new QueryData<SelectedItem>()
{
IsSorted = false,
IsFiltered = false,
IsAdvanceSearch = false,
IsSearch = !option.SearchText.IsNullOrWhiteSpace()
};
var items = datas.Skip((option.StartIndex)).Take(option.Count);
ret.TotalCount = datas.Count();
ret.Items = func(items).ToList();
return ret;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -4,7 +4,7 @@
<Import Project="..\..\PackNuget.props" />
<PropertyGroup>
<TargetFrameworks>net47;netstandard2.0;net6.0;net6.0-windows;net8.0;$(OtherTargetFrameworks);net8.0-windows;</TargetFrameworks>
<TargetFrameworks>net462;netstandard2.0;net6.0;net6.0-windows;net8.0;$(OtherTargetFrameworks);net8.0-windows;</TargetFrameworks>
<AssemblyName>ThingsGateway.NewLife.X</AssemblyName>
<RootNamespace>ThingsGateway.NewLife</RootNamespace>
<AssemblyTitle>工具核心库</AssemblyTitle>
@@ -12,9 +12,6 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>newlife.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
@@ -24,7 +21,7 @@
<None Remove="..\..\..\README.zh-CN.md" Pack="false" PackagePath="\" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net47' or '$(TargetFramework)'=='net5.0-windows' or '$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net7.0-windows' or '$(TargetFramework)'=='net8.0-windows'">
<PropertyGroup Condition="'$(TargetFramework)'=='net462' or '$(TargetFramework)'=='net5.0-windows' or '$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net7.0-windows' or '$(TargetFramework)'=='net8.0-windows'">
<DefineConstants>__WIN__</DefineConstants>
</PropertyGroup>
@@ -35,11 +32,12 @@
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<PackageReference Include="System.Memory" Version="4.6.3" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net47'">
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
<PackageReference Include="System.Memory" Version="4.6.3" />
<PackageReference Include="System.ValueTuple" Version="4.6.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net47'">
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
<Using Include="System.Net.Http" />
<Reference Include="Microsoft.VisualBasic" />
<Reference Include="System.Management" />
@@ -54,10 +52,10 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>
<PropertyGroup Condition="$(TargetFramework)=='net6.0'">
<PropertyGroup Condition="$(TargetFramework)=='net6.0' OR $(TargetFramework)=='net6.0-windows'">
<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework)=='net8.0'">
<PropertyGroup Condition="$(TargetFramework)=='net8.0' OR $(TargetFramework)=='net8.0-windows'">
<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
</PropertyGroup>
@@ -66,7 +64,7 @@
<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework)=='net47'">
<PropertyGroup Condition="$(TargetFramework)=='net462'">
<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants>
<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
</PropertyGroup>
@@ -88,6 +86,6 @@
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
</ItemGroup>-->
</Project>

View File

@@ -7,7 +7,6 @@
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Photino.NET" Version="4.0.16" />
</ItemGroup>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -24,7 +24,7 @@
<ItemGroup>
<PackageReference Include="SqlSugarCore.Dm" Version="8.8.2" />
<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.905" />
<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.1030" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.20" />
<!--<PackageReference Include="Microsoft.Data.Sqlite" Version="$(NET10Version)" />-->
<PackageReference Include="MySqlConnector" Version="2.4.0" />

View File

@@ -1,9 +1,9 @@
<Project>
<PropertyGroup>
<PluginVersion>10.12.17</PluginVersion>
<ProPluginVersion>10.12.17</ProPluginVersion>
<DefaultVersion>10.12.17</DefaultVersion>
<PluginVersion>10.12.24</PluginVersion>
<ProPluginVersion>10.12.24</ProPluginVersion>
<DefaultVersion>10.12.24</DefaultVersion>
<AuthenticationVersion>10.11.7</AuthenticationVersion>
<SourceGeneratorVersion>10.11.7</SourceGeneratorVersion>
<NET8Version>8.0.21</NET8Version>
@@ -22,7 +22,9 @@
<OtherTargetFrameworks>net10.0</OtherTargetFrameworks>
</PropertyGroup>
<PropertyGroup>
<RestoreEnablePackagePruning Condition="'$(TargetFramework)' == 'net462' "> false</RestoreEnablePackagePruning>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' != 'net8.0' ">
<PluginTargetFramework>net10.0</PluginTargetFramework>

View File

@@ -333,6 +333,10 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
/// <inheritdoc/>
private Task SendAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken token = default)
{
if(!channel.Online)
{
throw new InvalidOperationException("Channel is offline");
}
return SendAsync(this, sendMessage, channel, token);
static async PooledTask SendAsync(DeviceBase @this, ISendMessage sendMessage, IClientChannel channel, CancellationToken token)

View File

@@ -21,7 +21,7 @@
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Admin\ThingsGateway.NewLife.X\ThingsGateway.NewLife.X.csproj" />

View File

@@ -509,34 +509,16 @@ internal sealed class AlarmTask : IDisposable
{
scheduledTask.Change(100, 100);
}
ParallelOptions.CancellationToken = cancellation;
// 遍历设备变量列表
if (!GlobalData.AlarmEnableIdVariables.IsEmpty)
{
// 使用 Parallel.ForEach 执行指定的操作
Parallel.ForEach(GlobalData.AlarmEnableIdVariables, ParallelOptions, (item, state, index) =>
{
// 如果取消请求已经被触发,则结束任务
if (cancellation.IsCancellationRequested)
return;
// 如果该变量的报警功能未启用,则跳过该变量
if (!item.Value.AlarmEnable)
return;
// 如果该变量离线,则跳过该变量
if (!item.Value.IsOnline)
return;
// 对该变量进行报警分析
AlarmAnalysis(item.Value);
});
Parallel.ForEach(GlobalData.AlarmEnableIdVariables, ParallelOptions, Analysis);
}
else
{
//if (scheduledTask.Period != 5000)
// scheduledTask.Change(0, 5000); // 如果没有启用报警的变量则设置下次执行时间为5秒后
scheduledTask.SetNext(5000); // 如果没有启用报警的变量则设置下次执行时间为5秒后
}
@@ -552,6 +534,21 @@ internal sealed class AlarmTask : IDisposable
}
}
private static void Analysis(KeyValuePair<long, VariableRuntime> item, ParallelLoopState state, long index)
{
// 如果取消请求已经被触发,则结束任务
if (state.ShouldExitCurrentIteration)
return;
// 如果该变量的报警功能未启用,则跳过该变量
if (!item.Value.AlarmEnable)
return;
// 如果该变量离线,则跳过该变量
if (!item.Value.IsOnline)
return;
// 对该变量进行报警分析
AlarmAnalysis(item.Value);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -10,6 +10,7 @@
using Riok.Mapperly.Abstractions;
using ThingsGateway.Common;
using ThingsGateway.DB;
namespace ThingsGateway.Plugin.DB;

View File

@@ -171,10 +171,10 @@ public partial class MqttCollect : CollectBase
mqttClientSubscribeOptionsBuilder = mqttClientSubscribeOptionsBuilder.WithTopicFilter(
f => f.WithTopic(item));
}
var mqttClientSubscribeOptions = mqttClientSubscribeOptionsBuilder.Build();
if (mqttClientSubscribeOptions.TopicFilters.Count > 0)
_mqttSubscribeOptions = mqttClientSubscribeOptions;
}
var mqttClientSubscribeOptions = mqttClientSubscribeOptionsBuilder.Build();
if (mqttClientSubscribeOptions.TopicFilters.Count > 0)
_mqttSubscribeOptions = mqttClientSubscribeOptions;
return Task.FromResult(dataResult);
}

View File

@@ -20,6 +20,8 @@ using System.Diagnostics.CodeAnalysis;
using ThingsGateway.Extension;
using ThingsGateway.Foundation.OpcDa;
using ThingsGateway.Foundation.OpcDa.Rcw;
using ThingsGateway.Common;
#if Plugin

View File

@@ -35,6 +35,8 @@ using ThingsGateway.Razor;
using TouchSocket.Core;
using ThingsGateway.Common;
namespace ThingsGateway.Debug;
/// <summary>
@@ -73,7 +75,7 @@ public partial class OpcUaImportVariable
{
Items = BuildTreeItemList(await PopulateBranchAsync(ObjectIds.ObjectsFolder), RenderTreeItem).ToList();
ShowSkeleton = false;
await InvokeAsync(StateHasChanged);
return InvokeAsync(StateHasChanged);
});
}
await base.OnAfterRenderAsync(firstRender);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 177 KiB