Merge branch 'abp' of https://gitee.com/ccnetcore/Yi into abp
@@ -1,4 +1,4 @@
|
|||||||
<h1 align="center"><img align="left" height="150px" src="https://user-images.githubusercontent.com/68722157/138828506-f58b7c57-5e10-4178-8f7d-5d5e12050113.png"> Yi框架</h1>
|
<h1 align="center"><img align="left" height="150px" src="https://ccnetcore.com/prod-api/wwwroot/logo.png"> Yi框架</h1>
|
||||||
<h4 align="center">一套以用户体验出发的.Net8 Web开源框架</h4>
|
<h4 align="center">一套以用户体验出发的.Net8 Web开源框架</h4>
|
||||||
<h5 align="center">支持Abp.vNext 版本原生版本、Furion版本,前端后台接入Ruoyi Vue3.0</h5>
|
<h5 align="center">支持Abp.vNext 版本原生版本、Furion版本,前端后台接入Ruoyi Vue3.0</h5>
|
||||||
<h2 align="center">集大成者,终究轮子</h2>
|
<h2 align="center">集大成者,终究轮子</h2>
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
using System.Diagnostics;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml.Linq;
|
||||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.OpenApi.Any;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
using Volo.Abp.AspNetCore.Mvc;
|
using Volo.Abp.AspNetCore.Mvc;
|
||||||
@@ -75,12 +79,59 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
|
|||||||
{
|
{
|
||||||
[scheme] = new string[0]
|
[scheme] = new string[0]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
options.SchemaFilter<EnumSchemaFilter>();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Swagger文档枚举字段显示枚举属性和枚举值,以及枚举描述
|
||||||
|
/// </summary>
|
||||||
|
public class EnumSchemaFilter : ISchemaFilter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 实现接口
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model"></param>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
|
||||||
|
public void Apply(OpenApiSchema model, SchemaFilterContext context)
|
||||||
|
{
|
||||||
|
if (context.Type.IsEnum)
|
||||||
|
{
|
||||||
|
model.Enum.Clear();
|
||||||
|
model.Type = "string";
|
||||||
|
model.Format = null;
|
||||||
|
|
||||||
|
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
Enum.GetNames(context.Type)
|
||||||
|
.ToList()
|
||||||
|
.ForEach(name =>
|
||||||
|
{
|
||||||
|
Enum e = (Enum)Enum.Parse(context.Type, name);
|
||||||
|
var descrptionOrNull = GetEnumDescription(e);
|
||||||
|
model.Enum.Add(new OpenApiString(name));
|
||||||
|
stringBuilder.Append($"【枚举:{name}{(descrptionOrNull is null ? string.Empty : $"({descrptionOrNull})")}={Convert.ToInt64(Enum.Parse(context.Type, name))}】<br />");
|
||||||
|
});
|
||||||
|
model.Description= stringBuilder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetEnumDescription(Enum value)
|
||||||
|
{
|
||||||
|
var fieldInfo = value.GetType().GetField(value.ToString());
|
||||||
|
var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||||
|
return attributes.Length > 0 ? attributes[0].Description : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||||
|
|
||||||
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article
|
namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article
|
||||||
@@ -16,6 +17,8 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article
|
|||||||
[Required]
|
[Required]
|
||||||
public Guid DiscussId { get; set; }
|
public Guid DiscussId { get; set; }
|
||||||
|
|
||||||
|
public Guid ArticleParentId { get; set; }= Guid.Empty;
|
||||||
|
|
||||||
public ArticleImportTypeEnum ImportType { get; set; } = ArticleImportTypeEnum.Defalut;
|
public ArticleImportTypeEnum ImportType { get; set; } = ArticleImportTypeEnum.Defalut;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Mapster;
|
using Mapster;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@@ -143,7 +144,7 @@ namespace Yi.Framework.Bbs.Application.Services
|
|||||||
/// 导入文章
|
/// 导入文章
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task PostImportAsync(ArticleImprotDto input, [FromForm] IFormFileCollection file)
|
public async Task PostImportAsync([FromQuery] ArticleImprotDto input, [FromForm][Required] IFormFileCollection file)
|
||||||
{
|
{
|
||||||
var fileObjs = new List<FileObject>();
|
var fileObjs = new List<FileObject>();
|
||||||
if (file.Count > 0)
|
if (file.Count > 0)
|
||||||
@@ -161,14 +162,18 @@ namespace Yi.Framework.Bbs.Application.Services
|
|||||||
|
|
||||||
// 将字节转换成字符串
|
// 将字节转换成字符串
|
||||||
var content = Encoding.UTF8.GetString(bytes);
|
var content = Encoding.UTF8.GetString(bytes);
|
||||||
fileObjs.Add(new FileObject() { FileName=item.FileName,Content=content});
|
fileObjs.Add(new FileObject() { FileName = item.FileName, Content = content });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("未选择文件");
|
||||||
|
}
|
||||||
//使用简单工厂根据传入的类型进行判断
|
//使用简单工厂根据传入的类型进行判断
|
||||||
await _forumManager.PostImportAsync(input.DiscussId, fileObjs, input.ImportType);
|
await _forumManager.PostImportAsync(input.DiscussId, input.ArticleParentId, fileObjs, input.ImportType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
using System;
|
using System.ComponentModel;
|
||||||
using System.Collections.Generic;
|
using Newtonsoft.Json;
|
||||||
using System.Linq;
|
using Newtonsoft.Json.Converters;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Yi.Framework.Bbs.Domain.Shared.Enums
|
namespace Yi.Framework.Bbs.Domain.Shared.Enums
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public enum ArticleImportTypeEnum
|
public enum ArticleImportTypeEnum
|
||||||
{
|
{
|
||||||
//默认导入方式
|
[Description("默认导入方式")]
|
||||||
Defalut,
|
Defalut,
|
||||||
|
|
||||||
//vuePresss方式
|
[Description("vuePresss方式")]
|
||||||
VuePress
|
VuePress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Yi.Framework.Bbs.Domain.Entities;
|
||||||
|
using Yi.Framework.Bbs.Domain.Shared.Model;
|
||||||
|
|
||||||
|
namespace Yi.Framework.Bbs.Domain.Managers.ArticleImport
|
||||||
|
{
|
||||||
|
public abstract class AbstractArticleImport
|
||||||
|
{
|
||||||
|
public virtual List<ArticleEntity> Import(Guid discussId,Guid articleParentId, List<FileObject> fileObjs)
|
||||||
|
{
|
||||||
|
var articles = Convert(fileObjs);
|
||||||
|
articles.ForEach(article =>
|
||||||
|
{
|
||||||
|
article.DiscussId = discussId;
|
||||||
|
article.ParentId = articleParentId;
|
||||||
|
});
|
||||||
|
return articles;
|
||||||
|
}
|
||||||
|
public abstract List<ArticleEntity> Convert(List<FileObject> fileObjs);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Yi.Framework.Bbs.Domain.Entities;
|
||||||
|
using Yi.Framework.Bbs.Domain.Shared.Model;
|
||||||
|
|
||||||
|
namespace Yi.Framework.Bbs.Domain.Managers.ArticleImport
|
||||||
|
{
|
||||||
|
internal class DefaultArticleImport : AbstractArticleImport
|
||||||
|
{
|
||||||
|
public override List<ArticleEntity> Convert(List<FileObject> fileObjs)
|
||||||
|
{
|
||||||
|
return fileObjs.OrderBy(x => x.FileName).Select(x => new ArticleEntity { Name = x.FileName, Content = x.Content }).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Yi.Framework.Bbs.Domain.Entities;
|
||||||
|
using Yi.Framework.Bbs.Domain.Shared.Model;
|
||||||
|
|
||||||
|
namespace Yi.Framework.Bbs.Domain.Managers.ArticleImport
|
||||||
|
{
|
||||||
|
internal class VuePressArticleImport : AbstractArticleImport
|
||||||
|
{
|
||||||
|
public override List<ArticleEntity> Convert(List<FileObject> fileObjs)
|
||||||
|
{
|
||||||
|
//排序及处理目录名称
|
||||||
|
var fileNameHandler = fileObjs.OrderBy(x => x.FileName).Select(x =>
|
||||||
|
{
|
||||||
|
var f = new FileObject { Content = x.Content };
|
||||||
|
|
||||||
|
//除去数字
|
||||||
|
f.FileName = x.FileName.Split('.')[1];
|
||||||
|
return f;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//处理内容
|
||||||
|
var fileContentHandler= fileNameHandler.Select(x =>
|
||||||
|
{
|
||||||
|
var f = new FileObject { FileName = x.FileName };
|
||||||
|
var lines = x.Content.SplitToLines();
|
||||||
|
|
||||||
|
var num = 0;
|
||||||
|
var startIndex = 0;
|
||||||
|
for (int i = 0; i < lines.Length; i++)
|
||||||
|
{
|
||||||
|
if (lines[i] == "---")
|
||||||
|
{
|
||||||
|
num++;
|
||||||
|
if (num == 2)
|
||||||
|
{
|
||||||
|
startIndex = i;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
var linesRef = lines.ToList();
|
||||||
|
|
||||||
|
linesRef.RemoveRange(0, startIndex+1);
|
||||||
|
var result = string.Join(Environment.NewLine, linesRef);
|
||||||
|
f.Content = result;
|
||||||
|
return f;
|
||||||
|
});
|
||||||
|
|
||||||
|
var output = fileContentHandler.Select(x => new ArticleEntity() { Content = x.Content, Name = x.FileName }).ToList();
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Volo.Abp.Domain.Services;
|
using Volo.Abp.Domain.Services;
|
||||||
using Yi.Framework.Bbs.Domain.Entities;
|
using Yi.Framework.Bbs.Domain.Entities;
|
||||||
|
using Yi.Framework.Bbs.Domain.Managers.ArticleImport;
|
||||||
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
using Yi.Framework.Bbs.Domain.Shared.Enums;
|
||||||
using Yi.Framework.Bbs.Domain.Shared.Model;
|
using Yi.Framework.Bbs.Domain.Shared.Model;
|
||||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
@@ -16,11 +17,13 @@ namespace Yi.Framework.Bbs.Domain.Managers
|
|||||||
public readonly ISqlSugarRepository<DiscussEntity, Guid> _discussRepository;
|
public readonly ISqlSugarRepository<DiscussEntity, Guid> _discussRepository;
|
||||||
public readonly ISqlSugarRepository<PlateEntity, Guid> _plateEntityRepository;
|
public readonly ISqlSugarRepository<PlateEntity, Guid> _plateEntityRepository;
|
||||||
public readonly ISqlSugarRepository<CommentEntity, Guid> _commentRepository;
|
public readonly ISqlSugarRepository<CommentEntity, Guid> _commentRepository;
|
||||||
public ForumManager(ISqlSugarRepository<DiscussEntity, Guid> discussRepository, ISqlSugarRepository<PlateEntity, Guid> plateEntityRepository, ISqlSugarRepository<CommentEntity, Guid> commentRepository)
|
public readonly ISqlSugarRepository<ArticleEntity, Guid> _articleRepository;
|
||||||
|
public ForumManager(ISqlSugarRepository<DiscussEntity, Guid> discussRepository, ISqlSugarRepository<PlateEntity, Guid> plateEntityRepository, ISqlSugarRepository<CommentEntity, Guid> commentRepository, ISqlSugarRepository<ArticleEntity, Guid> articleRepository)
|
||||||
{
|
{
|
||||||
_discussRepository = discussRepository;
|
_discussRepository = discussRepository;
|
||||||
_plateEntityRepository = plateEntityRepository;
|
_plateEntityRepository = plateEntityRepository;
|
||||||
_commentRepository = commentRepository;
|
_commentRepository = commentRepository;
|
||||||
|
_articleRepository = articleRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
//主题是不能直接创建的,需要由领域服务统一创建
|
//主题是不能直接创建的,需要由领域服务统一创建
|
||||||
@@ -45,11 +48,30 @@ namespace Yi.Framework.Bbs.Domain.Managers
|
|||||||
/// 导入文章
|
/// 导入文章
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="discussId"></param>
|
/// <param name="discussId"></param>
|
||||||
|
/// <param name="articleParentId"></param>
|
||||||
/// <param name="fileObjs"></param>
|
/// <param name="fileObjs"></param>
|
||||||
/// <param name="importType"></param>
|
/// <param name="importType"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task PostImportAsync(Guid discussId, List<FileObject> fileObjs, ArticleImportTypeEnum importType)
|
public async Task PostImportAsync(Guid discussId,Guid articleParentId, List<FileObject> fileObjs, ArticleImportTypeEnum importType)
|
||||||
{
|
{
|
||||||
|
AbstractArticleImport abstractArticleImport = default;
|
||||||
|
switch (importType)
|
||||||
|
{
|
||||||
|
case ArticleImportTypeEnum.Defalut:
|
||||||
|
abstractArticleImport = new DefaultArticleImport();
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ArticleImportTypeEnum.VuePress:
|
||||||
|
abstractArticleImport = new VuePressArticleImport();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: abstractArticleImport = new DefaultArticleImport(); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var articleHandled = abstractArticleImport.Import(discussId, articleParentId, fileObjs);
|
||||||
|
|
||||||
|
//await _articleRepository.InsertManyAsync(articleHandled);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,6 +248,17 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
throw new UserFriendlyException("验证码错误");
|
throw new UserFriendlyException("验证码错误");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ValidateUserName(RegisterDto input)
|
||||||
|
{
|
||||||
|
// 正则表达式,匹配只包含数字和字母的字符串
|
||||||
|
string pattern = @"^[a-zA-Z0-9]+$";
|
||||||
|
|
||||||
|
bool isMatch = Regex.IsMatch(input.UserName, pattern);
|
||||||
|
if (!isMatch)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("用户名不能包含除【字母】与【数字】的其他字符");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注册,需要验证码通过
|
/// 注册,需要验证码通过
|
||||||
@@ -276,6 +287,10 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
{
|
{
|
||||||
throw new UserFriendlyException("密码需大于等于6位!");
|
throw new UserFriendlyException("密码需大于等于6位!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//效验用户名
|
||||||
|
ValidateUserName(input);
|
||||||
|
|
||||||
//效验验证码,根据电话号码获取 value,比对验证码已经uuid
|
//效验验证码,根据电话号码获取 value,比对验证码已经uuid
|
||||||
await ValidationPhoneCaptchaAsync(input);
|
await ValidationPhoneCaptchaAsync(input);
|
||||||
|
|
||||||
@@ -283,7 +298,7 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
|
|
||||||
//输入的用户名与电话号码都不能在数据库中存在
|
//输入的用户名与电话号码都不能在数据库中存在
|
||||||
UserEntity user = new();
|
UserEntity user = new();
|
||||||
var isExist = await _userRepository.IsAnyAsync(x =>x.UserName == input.UserName|| x.Phone == input.Phone);
|
var isExist = await _userRepository.IsAnyAsync(x => x.UserName == input.UserName || x.Phone == input.Phone);
|
||||||
if (isExist)
|
if (isExist)
|
||||||
{
|
{
|
||||||
throw new UserFriendlyException("用户已存在,注册失败");
|
throw new UserFriendlyException("用户已存在,注册失败");
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Newtonsoft.Json.Converters;
|
||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
using Volo.Abp.AspNetCore.Authentication.JwtBearer;
|
using Volo.Abp.AspNetCore.Authentication.JwtBearer;
|
||||||
using Volo.Abp.AspNetCore.Mvc;
|
using Volo.Abp.AspNetCore.Mvc;
|
||||||
@@ -65,6 +66,7 @@ namespace Yi.Abp.Web
|
|||||||
service.AddControllers().AddNewtonsoftJson(options =>
|
service.AddControllers().AddNewtonsoftJson(options =>
|
||||||
{
|
{
|
||||||
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
|
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
options.SerializerSettings.Converters.Add(new StringEnumConverter());
|
||||||
});
|
});
|
||||||
|
|
||||||
Configure<AbpAntiForgeryOptions>(options =>
|
Configure<AbpAntiForgeryOptions>(options =>
|
||||||
|
|||||||
BIN
Yi.Abp.Net8/src/Yi.Abp.Web/wwwroot/logo.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
readme/1.png
|
Before Width: | Height: | Size: 217 KiB After Width: | Height: | Size: 218 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 108 KiB |
BIN
readme/2.png
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 131 KiB |
BIN
readme/3.png
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 88 KiB |
BIN
readme/4.png
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 151 KiB |
BIN
readme/5.png
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 112 KiB |
BIN
readme/6.png
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 94 KiB |
BIN
readme/7.png
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 141 KiB |
BIN
readme/8.png
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 107 KiB |
BIN
readme/9.png
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 116 KiB |