Merge branch 'dev' into cms-kit/content-management

pull/6821/head
Ahmet 5 years ago
commit e8fa6e463c

@ -106,6 +106,12 @@ abp new Acme.BookStore
* `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D:\local-template` or `https://.../my-template-file.zip`).
* `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder.
* `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true` for EF Core and it is configured to use the SQL Server. If you want to use the EF Core, but need to change the DBMS, you can change it as [described here](Entity-Framework-Core-Other-DBMS.md) (after creating the solution).
* `--database-management-system` or `-dbms`: Sets the database management system. Default is **SQL Server**. `--connection-string` parameter should be set along with this parameter if you want to set any DBMS other than **SQL Server**. Supported DBMS's:
* `SqlServer`
* `MySQL`
* `SQLite`
* `Oracle-Devart`
* `PostgreSQL`
* `--local-framework-ref --abp-path`: Uses local projects references to the ABP framework instead of using the NuGet packages. This can be useful if you download the ABP Framework source code and have a local reference to the framework from your application.
* `--no-random-port`: Uses template's default ports.
@ -419,4 +425,4 @@ abp bundle [options]
* ```--working-directory``` or ```-wd```: Specifies the working directory. This option is useful when executing directory doesn't contain a Blazor project file.
* ```--force``` or ```-f```: Forces to build project before generating references.
`bundle` command reads the `appsettings.json` file inside the Blazor project for bundling options. For more details about managing style and script references in Blazor apps, see [Managing Global Scripts & Styles](UI/Blazor/Global-Scripts-Styles.md)
`bundle` command reads the `appsettings.json` file inside the Blazor project for bundling options. For more details about managing style and script references in Blazor apps, see [Managing Global Scripts & Styles](UI/Blazor/Global-Scripts-Styles.md)

@ -20,13 +20,15 @@ namespace MyProject.Web
public class MyProjectBrandingProvider : DefaultBrandingProvider
{
public override string AppName => "Book Store";
public override string LogoUrl => "logo.png";
}
}
````
The result will be like shown below:
![branding-appname](../../images/branding-appname.png)
![bookstore-added-logo](../../images/bookstore-added-logo.png)
`IBrandingProvider` has the following properties:
@ -38,8 +40,4 @@ The result will be like shown below:
## Overriding the Branding Area
The [Basic Theme](Basic-Theme.md) doesn't implement the logos. However, you can see the [UI Customization Guide](Customization-User-Interface.md) to learn how you can replace the branding area with a custom view component.
An example screenshot with an image is used in the branding area:
![bookstore-added-logo](../../images/bookstore-added-logo.png)
You can see the [UI Customization Guide](Customization-User-Interface.md) to learn how you can replace the branding area with a custom view component.

@ -63,7 +63,7 @@ A theme is simply a Razor Class Library.
### The Easy Way
The easiest way to create a new theme is to copy the [Basic Theme Source Code](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme) and customize it. Once you get a copy of the theme in your solution, remove the `Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic` NuGet package and reference to the local project.
The easiest way to create a new theme is to copy the [Basic Theme Source Code](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme) and customize it. Once you get a copy of the theme in your solution, remove the `Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme` NuGet package and reference to the local project.
### Global Styles / Scripts

@ -1920,7 +1920,7 @@ public class OrganizationAppService : ApplicationService
你可能想知道为什么付款逻辑代码不在`OrganizationManager`中.付款是非常**重要的事情**,我们不能**遗漏任何一次付款**.
它确实非常重要,但是,它不能放到领域服务中.我们可能还**其它用例**来创建组织但不收取任何费用.例如:
它确实非常重要,但是,它不能放到领域服务中.我们可能还**其它用例**来创建组织但不收取任何费用.例如:
* 管理员可以在后台管理系统创建新组织,而无需支付任何费用.
* 后台作业系统导入,集成,同步组织而无需支付费用.

@ -55,7 +55,8 @@ $.validator.defaults.ignore = ''; //TODO: Would be better if we can apply only f
function _createContainer() {
_removeContainer();
_$modalContainer = $('<div id="' + _modalId + 'Container' + '"></div>').appendTo('body');
_$modalContainer = $('<div id="' + _modalId + 'Container' + '"></div>');
$('body').prepend(_$modalContainer);
return _$modalContainer;
}

@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
@ -16,6 +17,7 @@ using Volo.Abp.Cli.ProjectBuilding;
using Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.Cli.ProjectBuilding.Templates.App;
using Volo.Abp.Cli.ProjectBuilding.Templates.Console;
using Volo.Abp.Cli.ProjectModification;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
@ -24,14 +26,17 @@ namespace Volo.Abp.Cli.Commands
{
public class NewCommand : IConsoleCommand, ITransientDependency
{
private readonly EfCoreMigrationRecreater _efCoreMigrationRecreater;
public ILogger<NewCommand> Logger { get; set; }
protected TemplateProjectBuilder TemplateProjectBuilder { get; }
public ITemplateInfoProvider TemplateInfoProvider { get; }
public NewCommand(TemplateProjectBuilder templateProjectBuilder
, ITemplateInfoProvider templateInfoProvider)
, ITemplateInfoProvider templateInfoProvider,
EfCoreMigrationRecreater efCoreMigrationRecreater)
{
_efCoreMigrationRecreater = efCoreMigrationRecreater;
TemplateProjectBuilder = templateProjectBuilder;
TemplateInfoProvider = templateInfoProvider;
@ -84,18 +89,31 @@ namespace Volo.Abp.Cli.Commands
Logger.LogInformation("Database provider: " + databaseProvider);
}
var uiFramework = GetUiFramework(commandLineArgs);
if (uiFramework != UiFramework.NotSpecified)
{
Logger.LogInformation("UI Framework: " + uiFramework);
}
var connectionString = GetConnectionString(commandLineArgs);
if (connectionString != null)
{
Logger.LogInformation("Connection string: " + connectionString);
}
var databaseManagementSystem = GetDatabaseManagementSystem(commandLineArgs);
if (databaseManagementSystem != DatabaseManagementSystem.NotSpecified)
{
Logger.LogInformation("DBMS: " + databaseManagementSystem);
}
if (databaseManagementSystem != DatabaseManagementSystem.NotSpecified
&& databaseManagementSystem != DatabaseManagementSystem.SQLServer
&& connectionString == null)
{
throw new CliUsageException($"Connection string must be set if a Database Management System other than SQLServer is set. Use \"--{Options.ConnectionString.Long}\" parameter to set connection string");
}
var uiFramework = GetUiFramework(commandLineArgs);
if (uiFramework != UiFramework.NotSpecified)
{
Logger.LogInformation("UI Framework: " + uiFramework);
}
var mobileApp = GetMobilePreference(commandLineArgs);
if (mobileApp != MobileApp.None)
{
@ -147,6 +165,7 @@ namespace Volo.Abp.Cli.Commands
template,
version,
databaseProvider,
databaseManagementSystem,
uiFramework,
mobileApp,
gitHubAbpLocalRepositoryPath,
@ -196,6 +215,8 @@ namespace Volo.Abp.Cli.Commands
}
}
ReCreateMigrationsIfNeeded(databaseProvider, databaseManagementSystem, outputFolder);
Logger.LogInformation($"'{projectName}' has been successfully created to '{outputFolder}'");
if (AppTemplateBase.IsAppTemplate(template ?? (await TemplateInfoProvider.GetDefaultAsync()).Name))
@ -205,9 +226,27 @@ namespace Volo.Abp.Cli.Commands
}
}
private void ReCreateMigrationsIfNeeded(DatabaseProvider databaseProvider, DatabaseManagementSystem databaseManagementSystem, string outputFolder)
{
if (databaseManagementSystem == DatabaseManagementSystem.NotSpecified || databaseManagementSystem == DatabaseManagementSystem.SQLServer)
{
return;
}
if (databaseProvider != DatabaseProvider.NotSpecified && databaseProvider != DatabaseProvider.EntityFrameworkCore)
{
return;
}
Logger.LogInformation($"Re-creating migrations... ({databaseManagementSystem})");
_efCoreMigrationRecreater.Recreate(outputFolder);
}
private void OpenThanksPage(UiFramework uiFramework, DatabaseProvider databaseProvider, bool tiered, bool commercial)
{
uiFramework = uiFramework == UiFramework.NotSpecified || uiFramework == UiFramework.None ? UiFramework.Mvc : uiFramework;
databaseProvider = databaseProvider == DatabaseProvider.NotSpecified ? DatabaseProvider.EntityFrameworkCore : databaseProvider;
var urlPrefix = commercial ? "commercial" : "www";
var tieredYesNo = tiered ? "yes" : "no";
@ -267,6 +306,7 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine("-ts|--template-source <template-source> (your local or network abp template source)");
sb.AppendLine("-csf|--create-solution-folder (default: true)");
sb.AppendLine("-cs|--connection-string <connection-string> (your database connection string)");
sb.AppendLine("--dbms <database-management-system> (your database management system. Requires --connection-string to be set)");
sb.AppendLine("--tiered (if supported by the template)");
sb.AppendLine("--no-ui (if supported by the template)");
sb.AppendLine("--no-random-port (Use template's default ports)");
@ -289,6 +329,7 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine(" abp new Acme.BookStore -ts \"D:\\localTemplate\\abp\"");
sb.AppendLine(" abp new Acme.BookStore -csf false");
sb.AppendLine(" abp new Acme.BookStore --local-framework-ref --abp-path \"D:\\github\\abp\"");
sb.AppendLine(" abp new Acme.BookStore --dbms mysql --connection-string \"Server=myServerName\\myInstanceName;Database=myDatabase;User Id=myUsername;Password=myPassword\"");
sb.AppendLine(" abp new Acme.BookStore --connection-string \"Server=myServerName\\myInstanceName;Database=myDatabase;User Id=myUsername;Password=myPassword\"");
sb.AppendLine("");
sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI");
@ -315,6 +356,34 @@ namespace Volo.Abp.Cli.Commands
}
}
protected virtual DatabaseManagementSystem GetDatabaseManagementSystem(CommandLineArgs commandLineArgs)
{
var optionValue = commandLineArgs.Options.GetOrNull(Options.DatabaseManagementSystem.Short, Options.DatabaseManagementSystem.Long);
if (optionValue == null)
{
return DatabaseManagementSystem.NotSpecified;
}
switch (optionValue.ToLowerInvariant())
{
case "sqlserver":
return DatabaseManagementSystem.SQLServer;
case "mysql":
return DatabaseManagementSystem.MySQL;
case "postgresql":
return DatabaseManagementSystem.PostgreSQL;
case "oracle-devart":
return DatabaseManagementSystem.OracleDevart;
case "sqlite":
return DatabaseManagementSystem.SQLite;
case "oracle": // Currently disabled. See https://github.com/abpframework/abp/issues/6513
// return DatabaseManagementSystem.Oracle;
default:
return DatabaseManagementSystem.NotSpecified;
}
}
protected virtual UiFramework GetUiFramework(CommandLineArgs commandLineArgs)
{
var optionValue = commandLineArgs.Options.GetOrNull(Options.UiFramework.Short, Options.UiFramework.Long);
@ -362,6 +431,12 @@ namespace Volo.Abp.Cli.Commands
public const string Long = "database-provider";
}
public static class DatabaseManagementSystem
{
public const string Short = "dbms";
public const string Long = "database-management-system";
}
public static class OutputFolder
{
public const string Short = "o";

@ -37,6 +37,7 @@ namespace Volo.Abp.Cli.Commands.Services
moduleName,
version,
DatabaseProvider.NotSpecified,
DatabaseManagementSystem.NotSpecified,
UiFramework.NotSpecified,
null,
gitHubAbpLocalRepositoryPath,

@ -0,0 +1,13 @@
namespace Volo.Abp.Cli.ProjectBuilding.Building
{
public enum DatabaseManagementSystem
{
NotSpecified,
SQLServer,
MySQL,
PostgreSQL,
Oracle,
OracleDevart,
SQLite
}
}

@ -0,0 +1,97 @@
using System;
using System.Linq;
using Volo.Abp.Cli.ProjectBuilding.Files;
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
{
public class DatabaseManagementSystemChangeStep : ProjectBuildPipelineStep
{
public override void Execute(ProjectBuildContext context)
{
switch (context.BuildArgs.DatabaseManagementSystem)
{
case DatabaseManagementSystem.MySQL:
ChangeEntityFrameworkCoreDependency(context,"Volo.Abp.EntityFrameworkCore.MySQL",
"Volo.Abp.EntityFrameworkCore.MySQL",
"AbpEntityFrameworkCoreMySQLModule");
AddMySqlServerVersion(context);
ChangeUseSqlServer(context,"UseMySQL", "UseMySql");
break;
case DatabaseManagementSystem.PostgreSQL:
ChangeEntityFrameworkCoreDependency(context,"Volo.Abp.EntityFrameworkCore.PostgreSql",
"Volo.Abp.EntityFrameworkCore.PostgreSql",
"AbpEntityFrameworkCorePostgreSqlModule");
ChangeUseSqlServer(context,"UseNpgsql");
break;
case DatabaseManagementSystem.Oracle:
ChangeEntityFrameworkCoreDependency(context,"Volo.Abp.EntityFrameworkCore.Oracle",
"Volo.Abp.EntityFrameworkCore.Oracle",
"AbpEntityFrameworkCoreOracleModule");
ChangeUseSqlServer(context,"UseOracle");
break;
case DatabaseManagementSystem.OracleDevart:
ChangeEntityFrameworkCoreDependency(context,"Volo.Abp.EntityFrameworkCore.Oracle.Devart",
"Volo.Abp.EntityFrameworkCore.Oracle.Devart",
"AbpEntityFrameworkCoreOracleDevartModule");
AdjustOracleDbContextOptionsBuilder(context);
ChangeUseSqlServer(context,"UseOracle");
break;
case DatabaseManagementSystem.SQLite:
ChangeEntityFrameworkCoreDependency(context,"Volo.Abp.EntityFrameworkCore.Sqlite",
"Volo.Abp.EntityFrameworkCore.Sqlite",
"AbpEntityFrameworkCoreSqliteModule");
ChangeUseSqlServer(context,"UseSqlite");
break;
default:
return;
}
}
private void AdjustOracleDbContextOptionsBuilder(ProjectBuildContext context)
{
var dbContextFactoryFile = context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));
dbContextFactoryFile.ReplaceText("new DbContextOptionsBuilder",
$"(DbContextOptionsBuilder<{context.BuildArgs.SolutionName.ProjectName}MigrationsDbContext>) new DbContextOptionsBuilder");
}
private void AddMySqlServerVersion(ProjectBuildContext context)
{
var dbContextFactoryFile = context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));
dbContextFactoryFile.ReplaceText("configuration.GetConnectionString(\"Default\")",
"configuration.GetConnectionString(\"Default\"), MySqlServerVersion.LatestSupportedServerVersion");
}
private void ChangeEntityFrameworkCoreDependency(ProjectBuildContext context, string newPackageName, string newModuleNamespace, string newModuleClass)
{
var efCoreProjectFile = context.Files.First(f => f.Name.EndsWith("EntityFrameworkCore.csproj", StringComparison.OrdinalIgnoreCase));
efCoreProjectFile.ReplaceText("Volo.Abp.EntityFrameworkCore.SqlServer", newPackageName);
var efCoreModuleClass = context.Files.First(f => f.Name.EndsWith("EntityFrameworkCoreModule.cs", StringComparison.OrdinalIgnoreCase));
efCoreModuleClass.ReplaceText("Volo.Abp.EntityFrameworkCore.SqlServer", newModuleNamespace);
efCoreModuleClass.ReplaceText("AbpEntityFrameworkCoreSqlServerModule", newModuleClass);
}
private void ChangeUseSqlServer(ProjectBuildContext context, string newUseMethodForEfModule, string newUseMethodForDbContext = null)
{
if (newUseMethodForDbContext == null)
{
newUseMethodForDbContext = newUseMethodForEfModule;
}
var oldUseMethod = "UseSqlServer";
var efCoreModuleClass = context.Files.First(f => f.Name.EndsWith("EntityFrameworkCoreModule.cs", StringComparison.OrdinalIgnoreCase));
efCoreModuleClass.ReplaceText(oldUseMethod, newUseMethodForEfModule);
var dbContextFactoryFile = context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));
dbContextFactoryFile.ReplaceText(oldUseMethod, newUseMethodForDbContext);
}
}
}

@ -25,6 +25,12 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building
pipeline.Steps.Add(new LicenseCodeReplaceStep());
}
if (context.Template.Name == AppTemplate.TemplateName ||
context.Template.Name == AppProTemplate.TemplateName)
{
pipeline.Steps.Add(new DatabaseManagementSystemChangeStep());
}
if ((context.BuildArgs.UiFramework == UiFramework.Mvc || context.BuildArgs.UiFramework == UiFramework.Blazor)
&& context.BuildArgs.MobileApp == MobileApp.None)
{

@ -17,6 +17,8 @@ namespace Volo.Abp.Cli.ProjectBuilding
public DatabaseProvider DatabaseProvider { get; set; }
public DatabaseManagementSystem DatabaseManagementSystem { get; set; }
public UiFramework UiFramework { get; set; }
public MobileApp? MobileApp { get; set; }
@ -41,6 +43,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
[CanBeNull] string templateName = null,
[CanBeNull] string version = null,
DatabaseProvider databaseProvider = DatabaseProvider.NotSpecified,
DatabaseManagementSystem databaseManagementSystem = DatabaseManagementSystem.NotSpecified,
UiFramework uiFramework = UiFramework.NotSpecified,
MobileApp? mobileApp = null,
[CanBeNull] string abpGitHubLocalRepositoryPath = null,
@ -53,6 +56,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
TemplateName = templateName;
Version = version;
DatabaseProvider = databaseProvider;
DatabaseManagementSystem = databaseManagementSystem;
UiFramework = uiFramework;
MobileApp = mobileApp;
AbpGitHubLocalRepositoryPath = abpGitHubLocalRepositoryPath;
@ -62,4 +66,4 @@ namespace Volo.Abp.Cli.ProjectBuilding
ConnectionString = connectionString;
}
}
}
}

@ -24,6 +24,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App
SwitchDatabaseProvider(context, steps);
DeleteUnrelatedProjects(context, steps);
RemoveUnnecessaryPorts(context, steps);
RandomizeSslPorts(context, steps);
RandomizeStringEncryption(context, steps);
UpdateNuGetConfig(context, steps);
@ -176,6 +177,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App
}
}
private static void RemoveUnnecessaryPorts(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
steps.Add(new RemoveUnnecessaryPortsStep());
}
private static void RandomizeSslPorts(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
if (context.BuildArgs.ExtraProperties.ContainsKey("no-random-port"))

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Volo.Abp.Cli.ProjectBuilding.Building;
namespace Volo.Abp.Cli.ProjectBuilding.Templates
{
public class RemoveUnnecessaryPortsStep : ProjectBuildPipelineStep
{
public override void Execute(ProjectBuildContext context)
{
var httpApiHostAppSettings = context.Files.FirstOrDefault(f => f.Name.EndsWith(".HttpApi.Host/appsettings.json"));
if (httpApiHostAppSettings == null)
{
return;
}
var portsToRemoveFromCors = new List<string>();
var appSettingsJson = JObject.Parse(httpApiHostAppSettings.Content);
var appJson = (JObject) appSettingsJson["App"];
if (context.BuildArgs.UiFramework != UiFramework.Angular)
{
appJson.Property("ClientUrl")?.Remove();
portsToRemoveFromCors.Add("4200");
}
if (context.BuildArgs.UiFramework != UiFramework.Blazor)
{
portsToRemoveFromCors.Add("44307");
}
if (appJson["CorsOrigins"] != null)
{
appJson["CorsOrigins"] = string.Join(",",
appJson["CorsOrigins"].ToString().Split(",").Where(u=> !portsToRemoveFromCors.Any(u.EndsWith))
);
}
httpApiHostAppSettings.SetContent(JsonConvert.SerializeObject(appSettingsJson, Formatting.Indented));
}
}
}

@ -0,0 +1,46 @@
using System;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.ProjectModification
{
public class EfCoreMigrationRecreater : ITransientDependency
{
public ILogger<EfCoreMigrationRecreater> Logger { get; set; }
public EfCoreMigrationRecreater()
{
Logger = NullLogger<EfCoreMigrationRecreater>.Instance;
}
public void Recreate(string solutionFolder)
{
if (Directory.Exists(Path.Combine(solutionFolder, "aspnet-core")))
{
solutionFolder = Path.Combine(solutionFolder, "aspnet-core");
}
var srcFolder = Path.Combine(solutionFolder, "src");
try
{
var migrationsFolder = Directory.GetDirectories(srcFolder).First(d => d.EndsWith(".EntityFrameworkCore.DbMigrations"));
Directory.Delete(Path.Combine(migrationsFolder, "Migrations"), true);
var migratorFolder = Directory.GetDirectories(srcFolder).First(d => d.EndsWith(".DbMigrator"));
var migratorProjectFile = Directory.GetFiles(migratorFolder).First(d => d.EndsWith(".DbMigrator.csproj"));
var addMigrationCommand = $"dotnet ef migrations add Initial --startup-project {migratorProjectFile}";
CmdHelper.RunCmd($"cd {migrationsFolder} && {addMigrationCommand}");
}
catch (Exception e)
{
Logger.LogWarning("Re-creating migrations process failed.");
throw e;
}
}
}
}

@ -57,6 +57,9 @@
"ProfileTab:Password": "Passwort ändern",
"ProfileTab:PersonalInfo": "Persönliche Informationen",
"ReturnToApplication": "Zur Anwendung zurückkehren",
"Volo.Account:InvalidEmailAddress": "Die angegebene E-Mail-Adresse kann nicht gefunden werden: {0}"
"Volo.Account:InvalidEmailAddress": "Die angegebene E-Mail-Adresse kann nicht gefunden werden: {0}",
"PasswordReset": "Passwort zurücksetzen",
"PasswordResetInfoInEmail": "Wir haben eine Anfrage zur Wiederherstellung des Kontos erhalten! Wenn Sie diese Anforderung initiiert haben, klicken Sie auf den folgenden Link, um Ihr Passwort zurückzusetzen.",
"ResetMyPassword": "Mein Passwort zurücksetzen"
}
}

@ -57,6 +57,9 @@
"ProfileTab:Password": "Change password",
"ProfileTab:PersonalInfo": "Personal info",
"ReturnToApplication": "Return to application",
"Volo.Account:InvalidEmailAddress": "Can not find the given email address: {0}"
"Volo.Account:InvalidEmailAddress": "Can not find the given email address: {0}",
"PasswordReset": "Password reset",
"PasswordResetInfoInEmail": "We received an account recovery request! If you initiated this request, click the following link to reset your password.",
"ResetMyPassword": "Reset my password"
}
}

@ -57,6 +57,9 @@
"ProfileTab:Password": "Cambiar contraseña",
"ProfileTab:PersonalInfo": "Información personal",
"ReturnToApplication": "Volver a la aplicación",
"Volo.Account:InvalidEmailAddress": "No se puede encontrar la dirección de e-mail solicitada: {0}"
"Volo.Account:InvalidEmailAddress": "No se puede encontrar la dirección de e-mail solicitada: {0}",
"PasswordReset": "Restablecer contraseña",
"PasswordResetInfoInEmail": "Recibimos una solicitud de recuperación de cuenta. Si inició esta solicitud, haga clic en el siguiente enlace para restablecer su contraseña.",
"ResetMyPassword": "Restablecer mi contraseña"
}
}

@ -49,6 +49,9 @@
"ResetPassword_Information": "Prosimo vnesite vaše novo geslo.",
"YourPasswordIsSuccessfullyReset": "Vaše geslo je uspešno ponastavljeno.",
"BackToLogin": "Nazaj na prijavo",
"Volo.Account:InvalidEmailAddress": "Navedenega e-poštnega naslova ni mogoče najti: {0}"
"Volo.Account:InvalidEmailAddress": "Navedenega e-poštnega naslova ni mogoče najti: {0}",
"PasswordReset": "Ponastavitev gesla",
"PasswordResetInfoInEmail": "Prejeta je bila zahteva za obnovitev računa! V kolikor ste vi sprožili zahtevo, kliknite na sledečo povezavo, da ponastavite geslo.",
"ResetMyPassword": "Ponastavi geslo"
}
}

@ -57,6 +57,9 @@
"ProfileTab:Password": "Şifre değiştir",
"ProfileTab:PersonalInfo": "Kişisel bilgiler",
"ReturnToApplication": "Uygulamaya geri dön",
"Volo.Account:InvalidEmailAddress": "Email adresi bulunamadı: {0}"
"Volo.Account:InvalidEmailAddress": "Email adresi bulunamadı: {0}",
"PasswordReset": "Şifre Sıfırlama",
"PasswordResetInfoInEmail": "Şifrenizi sıfırlamanız için bir talep aldık! Eğer bu talebi siz gerçekleştirmişseniz, şifrenizi sıfırlamak için bağlantıya tıklayın.",
"ResetMyPassword": "Şifremi sıfırla"
}
}

@ -57,6 +57,9 @@
"ProfileTab:Password": "更改密码",
"ProfileTab:PersonalInfo": "个人信息",
"ReturnToApplication": "返回到应用程序",
"Volo.Account:InvalidEmailAddress": "找不到给定的电子邮件地址:{0}"
"Volo.Account:InvalidEmailAddress": "找不到给定的电子邮件地址:{0}",
"PasswordReset": "重设密码",
"PasswordResetInfoInEmail": "我们收到了帐户恢复请求!如果你发起了此请求,请单击以下链接以重置密码.",
"ResetMyPassword": "重置我的密码"
}
}

@ -55,6 +55,9 @@
"BackToLogin": "返回登錄",
"ProfileTab:Password": "更改密碼",
"ProfileTab:PersonalInfo": "個人信息",
"ReturnToApplication": "返回到應用程序"
"ReturnToApplication": "返回到應用程序",
"PasswordReset": "重設密碼",
"PasswordResetInfoInEmail": "我們收到了帳戶恢復請求!如果你發起了此請求,請單擊以下鏈接以重置密碼.",
"ResetMyPassword": "重置我的密碼"
}
}

@ -1,10 +1,9 @@
import { HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
import { finalize } from 'rxjs/operators';
import { StartLoader, StopLoader } from '../actions/loader.actions';
import { SessionStateService } from '../services/session-state.service';
import { HttpWaitService } from '../services/http-wait.service';
@Injectable({
providedIn: 'root',
@ -12,20 +11,19 @@ import { SessionStateService } from '../services/session-state.service';
export class ApiInterceptor implements HttpInterceptor {
constructor(
private oAuthService: OAuthService,
private store: Store,
private sessionState: SessionStateService,
private httpWaitService: HttpWaitService,
) {}
intercept(request: HttpRequest<any>, next: HttpHandler) {
this.store.dispatch(new StartLoader(request));
this.httpWaitService.addRequest(request);
return next
.handle(
request.clone({
setHeaders: this.getAdditionalHeaders(request.headers),
}),
)
.pipe(finalize(() => this.store.dispatch(new StopLoader(request))));
.pipe(finalize(() => this.httpWaitService.deleteRequest(request)));
}
getAdditionalHeaders(existingHeaders?: HttpHeaders) {

@ -1,5 +1,5 @@
import { EventEmitter, Type } from '@angular/core';
import { Router, Routes } from '@angular/router';
import { Routes } from '@angular/router';
import { Subject } from 'rxjs';
import { eLayoutType } from '../enums/common';
import { Environment } from './environment';
@ -13,7 +13,7 @@ export namespace ABP {
}
export interface Test extends Partial<Root> {
baseHref?: Router;
baseHref?: string;
routes?: Routes;
}

@ -0,0 +1,44 @@
import { Injectable } from '@angular/core';
import { HttpRequest } from '@angular/common/http';
import { InternalStore } from '../utils/internal-store-utils';
export interface HttpWaitState {
requests: Set<HttpRequest<any>>;
}
@Injectable({
providedIn: 'root',
})
export class HttpWaitService {
protected store = new InternalStore<HttpWaitState>({ requests: new Set() });
getLoading() {
return !!this.store.state.requests.size;
}
getLoading$() {
return this.store.sliceState(({ requests }) => !!requests.size);
}
updateLoading$() {
return this.store.sliceUpdate(({ requests }) => !!requests.size);
}
clearLoading() {
this.store.patch({ requests: new Set() });
}
addRequest(request: HttpRequest<any>) {
const requests = this.store.state.requests;
requests.add(request);
this.store.patch({ requests });
}
deleteRequest(request: HttpRequest<any>) {
const requests = this.store.state.requests;
requests.delete(request);
this.store.patch({ requests });
}
// TODO: Add filter function
}

@ -4,6 +4,7 @@ export * from './config-state.service';
export * from './content-projection.service';
export * from './dom-insertion.service';
export * from './environment.service';
export * from './http-wait.service';
export * from './lazy-load.service';
export * from './list.service';
export * from './localization.service';
@ -12,7 +13,9 @@ export * from './permission.service';
export * from './profile-state.service';
export * from './profile.service';
export * from './replaceable-components.service';
export * from './resource-wait.service';
export * from './rest.service';
export * from './router-wait.service';
export * from './routes.service';
export * from './session-state.service';
export * from './subscription.service';

@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { concat, Observable, of, throwError } from 'rxjs';
import { delay, retryWhen, shareReplay, take, tap } from 'rxjs/operators';
import { LoadingStrategy } from '../strategies';
import { ResourceWaitService } from './resource-wait.service';
@Injectable({
providedIn: 'root',
@ -9,9 +10,11 @@ import { LoadingStrategy } from '../strategies';
export class LazyLoadService {
readonly loaded = new Map<string, HTMLScriptElement | HTMLLinkElement>();
constructor(private resourceWaitService: ResourceWaitService) {}
load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Observable<Event> {
if (this.loaded.has(strategy.path)) return of(new CustomEvent('load'));
this.resourceWaitService.addResource(strategy.path);
return strategy.createStream().pipe(
retryWhen(error$ =>
concat(
@ -19,7 +22,10 @@ export class LazyLoadService {
throwError(new CustomEvent('error')),
),
),
tap(() => this.loaded.set(strategy.path, strategy.element)),
tap(() => {
this.loaded.set(strategy.path, strategy.element);
this.resourceWaitService.deleteResource(strategy.path);
}),
delay(100),
shareReplay({ bufferSize: 1, refCount: true }),
);

@ -0,0 +1,41 @@
import { Injectable } from '@angular/core';
import { InternalStore } from '../utils/internal-store-utils';
export interface ResourceWaitState {
resources: Set<string>;
}
@Injectable({
providedIn: 'root',
})
export class ResourceWaitService {
private store = new InternalStore<ResourceWaitState>({ resources: new Set() });
getLoading() {
return !!this.store.state.resources.size;
}
getLoading$() {
return this.store.sliceState(({ resources }) => !!resources.size);
}
updateLoading$() {
return this.store.sliceUpdate(({ resources }) => !!resources.size);
}
clearLoading() {
this.store.patch({ resources: new Set() });
}
addResource(resource: string) {
const resources = this.store.state.resources;
resources.add(resource);
this.store.patch({ resources });
}
deleteResource(resource: string) {
const resources = this.store.state.resources;
resources.delete(resource);
this.store.patch({ resources });
}
}

@ -15,13 +15,13 @@ import { EnvironmentService } from './environment.service';
})
export class RestService {
constructor(
@Inject(CORE_OPTIONS) private options: ABP.Root,
private http: HttpClient,
private store: Store,
private environment: EnvironmentService,
@Inject(CORE_OPTIONS) protected options: ABP.Root,
protected http: HttpClient,
protected environment: EnvironmentService,
protected store: Store,
) {}
private getApiFromStore(apiName: string): string {
protected getApiFromStore(apiName: string): string {
return this.environment.getApiUrl(apiName);
}

@ -0,0 +1,53 @@
import { Injectable } from '@angular/core';
import {
NavigationCancel,
NavigationEnd,
NavigationError,
NavigationStart,
Router,
} from '@angular/router';
import { filter } from 'rxjs/operators';
import { InternalStore } from '../utils/internal-store-utils';
export interface RouterWaitState {
loading: boolean;
}
@Injectable({
providedIn: 'root',
})
export class RouterWaitService {
private store = new InternalStore<RouterWaitState>({ loading: false });
constructor(private router: Router) {
this.router.events
.pipe(
filter(
event =>
event instanceof NavigationStart ||
event instanceof NavigationEnd ||
event instanceof NavigationError ||
event instanceof NavigationCancel,
),
)
.subscribe(event => {
if (event instanceof NavigationStart) this.setLoading(true);
else this.setLoading(false);
});
}
getLoading() {
return this.store.state.loading;
}
getLoading$() {
return this.store.sliceState(({ loading }) => loading);
}
updateLoading$() {
return this.store.sliceUpdate(({ loading }) => loading);
}
setLoading(loading: boolean) {
this.store.patch({ loading });
}
}

@ -1,31 +1,29 @@
import { HttpRequest } from '@angular/common/http';
import { SpyObject } from '@ngneat/spectator';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { Store } from '@ngxs/store';
import { OAuthService } from 'angular-oauth2-oidc';
import { Subject, timer } from 'rxjs';
import { StartLoader, StopLoader } from '../actions';
import { ApiInterceptor } from '../interceptors';
import { SessionStateService } from '../services';
import { HttpWaitService, SessionStateService } from '../services';
describe('ApiInterceptor', () => {
let spectator: SpectatorService<ApiInterceptor>;
let interceptor: ApiInterceptor;
let store: SpyObject<Store>;
let oauthService: SpyObject<OAuthService>;
let sessionState: SpyObject<SessionStateService>;
let httpWaitService: SpyObject<HttpWaitService>;
const createService = createServiceFactory({
service: ApiInterceptor,
mocks: [OAuthService, Store, SessionStateService],
mocks: [OAuthService, SessionStateService],
});
beforeEach(() => {
spectator = createService();
interceptor = spectator.service;
store = spectator.inject(Store);
sessionState = spectator.inject(SessionStateService);
oauthService = spectator.inject(OAuthService);
httpWaitService = spectator.inject(HttpWaitService);
});
it('should add headers to http request', done => {
@ -52,8 +50,9 @@ describe('ApiInterceptor', () => {
handleRes$.complete();
});
it('should dispatch the loader', done => {
const spy = jest.spyOn(store, 'dispatch');
it('should call http wait services add request and delete request', done => {
const spyAddRequest = jest.spyOn(httpWaitService, 'addRequest');
const spyDeleteRequest = jest.spyOn(httpWaitService, 'deleteRequest');
const request = new HttpRequest('GET', 'https://abp.io');
const handleRes$ = new Subject();
@ -70,8 +69,8 @@ describe('ApiInterceptor', () => {
handleRes$.complete();
timer(0).subscribe(() => {
expect(spy.mock.calls[0][0] instanceof StartLoader).toBeTruthy();
expect(spy.mock.calls[1][0] instanceof StopLoader).toBeTruthy();
expect(spyAddRequest).toHaveBeenCalled();
expect(spyDeleteRequest).toHaveBeenCalled();
done();
});
});

@ -2,10 +2,12 @@ import { of, throwError } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { LazyLoadService } from '../services/lazy-load.service';
import { ScriptLoadingStrategy } from '../strategies';
import { ResourceWaitService } from '../services';
describe('LazyLoadService', () => {
describe('#load', () => {
const service = new LazyLoadService();
const resourceWaitService = new ResourceWaitService();
const service = new LazyLoadService(resourceWaitService);
const strategy = new ScriptLoadingStrategy('http://example.com/');
afterEach(() => {
@ -58,7 +60,8 @@ describe('LazyLoadService', () => {
});
describe('#remove', () => {
const service = new LazyLoadService();
const resourceWaitService = new ResourceWaitService();
const service = new LazyLoadService(resourceWaitService);
it('should remove an already lazy loaded element and return true', () => {
const script = document.createElement('script');

@ -4,12 +4,14 @@ import {
coreOptionsFactory,
CORE_OPTIONS,
LocalizationPipe,
RestService,
} from '@abp/ng.core';
import { APP_BASE_HREF } from '@angular/common';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { provideRoutes } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { MockLocalizationPipe } from './pipes/mock-localization.pipe';
import { MockRestService } from './services/mock-rest.service';
/**
* CoreTestingModule is the module that will be used in tests
@ -21,7 +23,7 @@ import { MockLocalizationPipe } from './pipes/mock-localization.pipe';
declarations: [MockLocalizationPipe],
})
export class CoreTestingModule {
static forTest(
static withConfig(
{ baseHref = '/', routes = [], ...options } = {} as ABP.Test,
): ModuleWithProviders<CoreTestingModule> {
return {
@ -41,6 +43,10 @@ export class CoreTestingModule {
provide: LocalizationPipe,
useClass: MockLocalizationPipe,
},
{
provide: RestService,
useClass: MockRestService,
},
provideRoutes(routes),
],
};

@ -0,0 +1 @@
export * from './mock-rest.service';

@ -0,0 +1,21 @@
import { ABP, CORE_OPTIONS, EnvironmentService, RestService } from '@abp/ng.core';
import { HttpClient } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class MockRestService extends RestService {
constructor(
@Inject(CORE_OPTIONS) protected options: ABP.Root,
protected http: HttpClient,
protected environment: EnvironmentService,
) {
super(options, http, environment, null);
}
handleError(err: any): Observable<any> {
return throwError(err);
}
}

@ -1,9 +1,7 @@
import { StartLoader, StopLoader, SubscriptionService } from '@abp/ng.core';
import { HttpWaitService, RouterWaitService, SubscriptionService } from '@abp/ng.core';
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router';
import { Actions, ofActionSuccessful } from '@ngxs/store';
import { Subscription, timer } from 'rxjs';
import { filter } from 'rxjs/operators';
import { Router } from '@angular/router';
import { combineLatest, Subscription, timer } from 'rxjs';
@Component({
selector: 'abp-loader-bar',
@ -43,18 +41,14 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
progressLevel = 0;
interval: Subscription;
interval = new Subscription();
timer: Subscription;
timer = new Subscription();
intervalPeriod = 350;
stopDelay = 800;
@Input()
filter = (action: StartLoader | StopLoader) =>
action.payload.url.indexOf('openid-configuration') < 0;
private readonly clearProgress = () => {
this.progressLevel = 0;
this.cdRef.detectChanges();
@ -78,63 +72,47 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
}
constructor(
private actions: Actions,
private router: Router,
private cdRef: ChangeDetectorRef,
private subscription: SubscriptionService,
private httpWaitService: HttpWaitService,
private routerWaiterService: RouterWaitService,
) {}
private subscribeToLoadActions() {
this.subscription.addOne(
this.actions.pipe(ofActionSuccessful(StartLoader, StopLoader), filter(this.filter)),
action => {
if (action instanceof StartLoader) this.startLoading();
else this.stopLoading();
},
);
ngOnInit() {
this.subscribeLoading();
}
private subscribeToRouterEvents() {
subscribeLoading() {
this.subscription.addOne(
this.router.events.pipe(
filter(
event =>
event instanceof NavigationStart ||
event instanceof NavigationEnd ||
event instanceof NavigationError,
),
),
event => {
if (event instanceof NavigationStart) this.startLoading();
combineLatest([this.httpWaitService.getLoading$(), this.routerWaiterService.getLoading$()]),
([httpLoading, routerLoading]) => {
if (httpLoading || routerLoading) this.startLoading();
else this.stopLoading();
},
);
}
ngOnInit() {
this.subscribeToLoadActions();
this.subscribeToRouterEvents();
}
ngOnDestroy() {
if (this.interval) this.interval.unsubscribe();
this.interval.unsubscribe();
}
startLoading() {
if (this.isLoading || (this.interval && !this.interval.closed)) return;
if (this.isLoading || !this.interval.closed) return;
this.isLoading = true;
this.progressLevel = 0;
this.interval = timer(0, this.intervalPeriod).subscribe(this.reportProgress);
this.timer.unsubscribe();
}
stopLoading() {
if (this.interval) this.interval.unsubscribe();
this.interval.unsubscribe();
this.progressLevel = 100;
this.isLoading = false;
if (this.timer && !this.timer.closed) return;
if (!this.timer.closed) return;
this.timer = timer(this.stopDelay).subscribe(this.clearProgress);
}

@ -74,7 +74,6 @@ export class ErrorHandler {
private actions: Actions,
private router: Router,
private confirmationService: ConfirmationService,
private appRef: ApplicationRef,
private cfRes: ComponentFactoryResolver,
private rendererFactory: RendererFactory2,
private injector: Injector,
@ -281,14 +280,16 @@ export class ErrorHandler {
}
this.componentRef.instance.hideCloseIcon = this.httpErrorConfig.errorScreen.hideCloseIcon;
const appRef = this.injector.get(ApplicationRef);
if (this.canCreateCustomError(instance.status as ErrorScreenErrorCodes)) {
this.componentRef.instance.cfRes = this.cfRes;
this.componentRef.instance.appRef = this.appRef;
this.componentRef.instance.appRef = appRef;
this.componentRef.instance.injector = this.injector;
this.componentRef.instance.customComponent = this.httpErrorConfig.errorScreen.component;
}
this.appRef.attachView(this.componentRef.hostView);
appRef.attachView(this.componentRef.hostView);
renderer.appendChild(host, (this.componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0]);
const destroy$ = new Subject<void>();

@ -13,7 +13,7 @@ import { ConfirmationService } from '../services';
exports: [ConfirmationComponent],
entryComponents: [ConfirmationComponent],
declarations: [ConfirmationComponent],
imports: [CoreTestingModule.forTest()],
imports: [CoreTestingModule.withConfig()],
})
export class MockModule {}
@ -22,7 +22,7 @@ describe('ConfirmationService', () => {
let service: ConfirmationService;
const createService = createServiceFactory({
service: ConfirmationService,
imports: [NgxsModule.forRoot(), CoreTestingModule.forTest(), MockModule],
imports: [NgxsModule.forRoot(), CoreTestingModule.withConfig(), MockModule],
});
beforeEach(() => {

@ -31,7 +31,7 @@ const CONFIRMATION_BUTTONS = {
describe('ErrorHandler', () => {
const createService = createServiceFactory({
service: ErrorHandler,
imports: [NgxsModule.forRoot([]), CoreTestingModule.forTest(), MockModule],
imports: [NgxsModule.forRoot([]), CoreTestingModule.withConfig(), MockModule],
mocks: [OAuthService],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },

@ -1,47 +1,37 @@
import {
Router,
RouteReuseStrategy,
NavigationStart,
NavigationEnd,
NavigationError,
} from '@angular/router';
import { createHostFactory, SpectatorHost, SpyObject } from '@ngneat/spectator/jest';
import { Actions, NgxsModule, Store } from '@ngxs/store';
import { Subject, Subscription, Observable, Subscriber, timer } from 'rxjs';
import { NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router';
import { createComponentFactory, Spectator, SpyObject } from '@ngneat/spectator/jest';
import { Subject, timer } from 'rxjs';
import { LoaderBarComponent } from '../components/loader-bar/loader-bar.component';
import { StartLoader, StopLoader, SubscriptionService } from '@abp/ng.core';
import { HttpWaitService, SubscriptionService } from '@abp/ng.core';
import { HttpRequest } from '@angular/common/http';
describe('LoaderBarComponent', () => {
let spectator: SpectatorHost<LoaderBarComponent>;
let spectator: Spectator<LoaderBarComponent>;
let router: SpyObject<Router>;
const events$ = new Subject();
const createHost = createHostFactory({
const createComponent = createComponentFactory({
component: LoaderBarComponent,
mocks: [Router],
imports: [NgxsModule.forRoot()],
detectChanges: false,
providers: [SubscriptionService],
providers: [SubscriptionService, { provide: Router, useValue: { events: events$ } }],
});
beforeEach(() => {
spectator = createHost('<abp-loader-bar></abp-loader-bar>');
spectator = createComponent({});
spectator.component.intervalPeriod = 1;
spectator.component.stopDelay = 1;
router = spectator.inject(Router);
(router as any).events = events$;
});
it('should initial variable values are correct', () => {
spectator.component.interval = new Subscription();
expect(spectator.component.containerClass).toBe('abp-loader-bar');
expect(spectator.component.color).toBe('#77b6ff');
});
it('should increase the progressLevel', done => {
spectator.detectChanges();
spectator.inject(Store).dispatch(new StartLoader(new HttpRequest('GET', 'test')));
const httpWaitService = spectator.inject(HttpWaitService);
httpWaitService.addRequest(new HttpRequest('GET', 'test'));
spectator.detectChanges();
setTimeout(() => {
expect(spectator.component.progressLevel > 0).toBeTruthy();
@ -51,7 +41,8 @@ describe('LoaderBarComponent', () => {
test.skip('should be interval unsubscribed', done => {
spectator.detectChanges();
spectator.inject(Store).dispatch(new StartLoader(new HttpRequest('GET', 'test')));
const httpWaitService = spectator.inject(HttpWaitService);
httpWaitService.addRequest(new HttpRequest('GET', 'test'));
expect(spectator.component.interval.closed).toBe(false);
timer(400).subscribe(() => {
@ -62,11 +53,11 @@ describe('LoaderBarComponent', () => {
it('should start and stop the loading with navigation', done => {
spectator.detectChanges();
(router as any).events.next(new NavigationStart(1, 'test'));
events$.next(new NavigationStart(1, 'test'));
expect(spectator.component.interval.closed).toBe(false);
(router as any).events.next(new NavigationEnd(1, 'test', 'test'));
(router as any).events.next(new NavigationError(1, 'test', 'test'));
events$.next(new NavigationEnd(1, 'test', 'test'));
events$.next(new NavigationError(1, 'test', 'test'));
expect(spectator.component.progressLevel).toBe(100);
timer(2).subscribe(() => {
@ -77,10 +68,10 @@ describe('LoaderBarComponent', () => {
it('should stop the loading with navigation', done => {
spectator.detectChanges();
(router as any).events.next(new NavigationStart(1, 'test'));
events$.next(new NavigationStart(1, 'test'));
expect(spectator.component.interval.closed).toBe(false);
spectator.inject(Store).dispatch(new StopLoader(new HttpRequest('GET', 'test')));
events$.next(new NavigationEnd(1, 'testend', 'testend'));
expect(spectator.component.progressLevel).toBe(100);
timer(2).subscribe(() => {
@ -92,8 +83,8 @@ describe('LoaderBarComponent', () => {
describe('#startLoading', () => {
it('should return when isLoading is true', done => {
spectator.detectChanges();
(router as any).events.next(new NavigationStart(1, 'test'));
(router as any).events.next(new NavigationStart(1, 'test'));
events$.next(new NavigationStart(1, 'test'));
events$.next(new NavigationStart(1, 'test'));
done();
});
});

@ -11,7 +11,7 @@ import { ToasterService } from '../services/toaster.service';
exports: [ToastContainerComponent],
entryComponents: [ToastContainerComponent],
declarations: [ToastContainerComponent, ToastComponent],
imports: [CoreTestingModule.forTest()],
imports: [CoreTestingModule.withConfig()],
})
export class MockModule {}
const toastClassPrefix = 'abp-toast';
@ -21,7 +21,7 @@ describe('ToasterService', () => {
let service: ToasterService;
const createService = createServiceFactory({
service: ToasterService,
imports: [NgxsModule.forRoot(), CoreTestingModule.forTest(), MockModule],
imports: [NgxsModule.forRoot(), CoreTestingModule.withConfig(), MockModule],
});
beforeEach(() => {

@ -27,7 +27,7 @@ import { initLazyStyleHandler } from './handlers/lazy-style.handler';
import { RootParams } from './models/common';
import { THEME_SHARED_ROUTE_PROVIDERS } from './providers/route.provider';
import { THEME_SHARED_APPEND_CONTENT } from './tokens/append-content.token';
import { HTTP_ERROR_CONFIG, httpErrorConfigFactory } from './tokens/http-error.token';
import { httpErrorConfigFactory, HTTP_ERROR_CONFIG } from './tokens/http-error.token';
import { DateParserFormatter } from './utils/date-parser-formatter';
const declarationsWithExports = [
@ -48,17 +48,11 @@ const declarationsWithExports = [
LoadingDirective,
TableSortDirective,
];
@NgModule({
imports: [CoreModule, NgxDatatableModule, NgxValidateCoreModule, NgbPaginationModule],
declarations: [
...declarationsWithExports,
HttpErrorWrapperComponent,
ModalContainerComponent,
],
exports: [
NgxDatatableModule,
...declarationsWithExports,
],
declarations: [...declarationsWithExports, HttpErrorWrapperComponent, ModalContainerComponent],
exports: [NgxDatatableModule, ...declarationsWithExports],
providers: [DatePipe],
entryComponents: [
HttpErrorWrapperComponent,
@ -68,13 +62,23 @@ const declarationsWithExports = [
ConfirmationComponent,
],
})
export class ThemeSharedModule {
constructor(private errorHandler: ErrorHandler) {}
export class BaseThemeSharedModule {}
@NgModule({
imports: [BaseThemeSharedModule],
exports: [BaseThemeSharedModule],
})
export class ThemeSharedModule {
static forRoot(options = {} as RootParams): ModuleWithProviders<ThemeSharedModule> {
return {
ngModule: ThemeSharedModule,
providers: [
{
provide: APP_INITIALIZER,
multi: true,
deps: [ErrorHandler],
useFactory: noop,
},
THEME_SHARED_ROUTE_PROVIDERS,
{
provide: APP_INITIALIZER,

@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/theme-shared/testing",
"lib": {
"entryFile": "src/public-api.ts"
}
}

@ -0,0 +1,27 @@
import {
BaseThemeSharedModule,
DateParserFormatter,
THEME_SHARED_ROUTE_PROVIDERS,
} from '@abp/ng.theme.shared';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing';
import { NgbDateParserFormatter } from '@ng-bootstrap/ng-bootstrap';
/**
* ThemeSharedTestingModule is the module that will be used in tests
*/
@NgModule({
exports: [RouterTestingModule, BaseThemeSharedModule],
imports: [RouterTestingModule, BaseThemeSharedModule],
})
export class ThemeSharedTestingModule {
static withConfig(): ModuleWithProviders<ThemeSharedTestingModule> {
return {
ngModule: ThemeSharedTestingModule,
providers: [
THEME_SHARED_ROUTE_PROVIDERS,
{ provide: NgbDateParserFormatter, useClass: DateParserFormatter },
],
};
}
}

@ -0,0 +1 @@
export * from './lib/theme-shared-testing.module';

@ -2,12 +2,12 @@
# yarn lockfile v1
"@abp/ng.core@~4.1.0-rc.1":
version "4.1.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-4.1.0-rc.1.tgz#efbd35e8df0731e75b2195149269b0228ec4ae42"
integrity sha512-WhXHMlDSbYled+mW8faMW8qHUGVErh/E/3eOgfvhx4N2tGGs5w+A4EfjNVBBROcoLDwiX2q1L88WeNnrK1+OIQ==
"@abp/ng.core@~4.1.0-rc.2":
version "4.1.0-rc.2"
resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-4.1.0-rc.2.tgz#5fe53658965af66fae655dfce4bf6a7ad7105032"
integrity sha512-NcjOn1HYIdLTdHPbWTiLywPp1jQ+zmoeknnb0q/JfUUDn5r5kPrCf85xiZBUo8e+2ViB5vXvkM5lOOD5RfES6A==
dependencies:
"@abp/utils" "^4.0.1"
"@abp/utils" "^4.1.0-rc.1"
"@angular/localize" "~10.0.10"
"@ngxs/store" "^3.7.0"
angular-oauth2-oidc "^10.0.0"
@ -17,35 +17,35 @@
ts-toolbelt "6.15.4"
tslib "^2.0.0"
"@abp/ng.feature-management@~4.1.0-rc.1":
version "4.1.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-4.1.0-rc.1.tgz#5237db2e5a0ea5a6511941864fa721a303b5612c"
integrity sha512-/9GP6ze127uMkHB2QxWJC2MXiEMTLtBhM0DXYlPZKI+Y+FrVOBoUTI3gNz+AqWFwbBAA4PqfVuThhoja5b29Sg==
"@abp/ng.feature-management@~4.1.0-rc.2":
version "4.1.0-rc.2"
resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-4.1.0-rc.2.tgz#02e760bfa913276e694dbff545e39210a69b21bd"
integrity sha512-vf//thBD3ve8tkmAIan2FqQeHrO/sP8NVV83noGVMTpqKIAWIu7PBuSL3YqcNVOVxHLsO6EIwr63OAJytlvH6A==
dependencies:
"@abp/ng.theme.shared" "~4.1.0-rc.1"
"@abp/ng.theme.shared" "~4.1.0-rc.2"
tslib "^2.0.0"
"@abp/ng.identity@~4.1.0-rc.1":
version "4.1.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-4.1.0-rc.1.tgz#932fd10482e82400a87927d200c2d3c850cccef4"
integrity sha512-uh7Fy+X2qGiNZ58g2c1GkmJL9VnMPLsKzg9EugNV2VxbGYg4/sNmVvAeIV66x2hpDXETHCjm+7lYuZsqI/5Vew==
"@abp/ng.identity@~4.1.0-rc.2":
version "4.1.0-rc.2"
resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-4.1.0-rc.2.tgz#381d956cebe2f3c272c59567ff424d3729adb6aa"
integrity sha512-4Wnx/iXAOgdexDYuNTV+DBdnjyfhcgca1ZiUSuEnSVDKePq0xvpGeKvpXzhhm9T3uXNsqYwqNq/X5jK3jd6mZg==
dependencies:
"@abp/ng.permission-management" "~4.1.0-rc.1"
"@abp/ng.theme.shared" "~4.1.0-rc.1"
"@abp/ng.permission-management" "~4.1.0-rc.2"
"@abp/ng.theme.shared" "~4.1.0-rc.2"
tslib "^2.0.0"
"@abp/ng.permission-management@~4.1.0-rc.1":
version "4.1.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-4.1.0-rc.1.tgz#de810f1d50f901cffb15f59756127e83109e79f7"
integrity sha512-AgPpmBKDYnMKmMXoiU9ewGKtn5hH9frO5tV0wjz46BjmO9kIwyap3BBeCFcZjhgVLxZnXi8D0WuzZW7mgQARIQ==
"@abp/ng.permission-management@~4.1.0-rc.2":
version "4.1.0-rc.2"
resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-4.1.0-rc.2.tgz#b74402241886e156a0e72baddfbc0ebebb7576aa"
integrity sha512-6/Z/8KGKkAflfk6CoXk92StEELuP0xkwj2dvvFvnGpr2od9ELGWWqtiJ5Pjz6i5FQvrLdqcHqPh8x/vgr7QV9w==
dependencies:
"@abp/ng.theme.shared" "~4.1.0-rc.1"
"@abp/ng.theme.shared" "~4.1.0-rc.2"
tslib "^2.0.0"
"@abp/ng.schematics@~4.1.0-rc.1":
version "4.1.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/ng.schematics/-/ng.schematics-4.1.0-rc.1.tgz#42173b847cfc2f293245a96fe4732a9049b8ab7c"
integrity sha512-FU5Mat3ki6ARDmOK5WC4/AMXJC+61EknuH44SE9Y9PraXAYkJk+2Ha+1qAg+VLBQcEEVWLxIqMp+2Soc6d6oow==
"@abp/ng.schematics@~4.1.0-rc.2":
version "4.1.0-rc.2"
resolved "https://registry.yarnpkg.com/@abp/ng.schematics/-/ng.schematics-4.1.0-rc.2.tgz#31dada6fc1f69d0198f4fb3801b6009d6a0a0c09"
integrity sha512-yVi/C1IbwcOQfXUAe8FSlekdNfoqi/UQvTBQ19h/HGBBDxhqbv6QKjhkaVs/+EOAW8WHWCufyVtQ2/y/jrCeog==
dependencies:
"@angular-devkit/core" "~11.0.2"
"@angular-devkit/schematics" "~11.0.2"
@ -53,37 +53,37 @@
jsonc-parser "^2.3.0"
typescript "~3.9.2"
"@abp/ng.setting-management@~4.1.0-rc.1":
version "4.1.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-4.1.0-rc.1.tgz#d35083c211089683c4fb07f88523c01e3e5d8ce0"
integrity sha512-qKLV2IWkHbK5ohHMxbdzPqE1pvPPatmV0EidE79Rk4j5+WZXOXtNz5GEA2vLmF3CsuVjX90cJQdtY1bdZpvdVQ==
"@abp/ng.setting-management@~4.1.0-rc.2":
version "4.1.0-rc.2"
resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-4.1.0-rc.2.tgz#4b61bff63fa9d68be5f1c7b8c47049cc1bd8b5a8"
integrity sha512-Df2QS0bsytytUSkQ0KrI/m4pVTpQS708fwl8t3KcpeQ4hlgdg6LtTmhUaY/j6k8M1RJHaKvjPE9Jg6R2daLNGg==
dependencies:
"@abp/ng.theme.shared" "~4.1.0-rc.1"
"@abp/ng.theme.shared" "~4.1.0-rc.2"
tslib "^2.0.0"
"@abp/ng.tenant-management@~4.1.0-rc.1":
version "4.1.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-4.1.0-rc.1.tgz#be6740c8a7ac844c7100a46fb88bc96ce386e17d"
integrity sha512-OymGkg5BzHiraUC0FcWPm6JN84FgYNZ3YuLTAZd/iHmqQVoj51ymTK8dBOn7sw49vWlAj/WvGN4Z9tT/JS40zg==
"@abp/ng.tenant-management@~4.1.0-rc.2":
version "4.1.0-rc.2"
resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-4.1.0-rc.2.tgz#b75b60db124e5246476821da2bfeebd5e5da09b6"
integrity sha512-AnDiMa/M4xSwiSXjyIsBY13BNxm+JMhWiIEb9WAPlk8IUaNDJZH9zHJH/HQSUHdy6dFCmutl5Ezi6U1J9ZHpPQ==
dependencies:
"@abp/ng.feature-management" "~4.1.0-rc.1"
"@abp/ng.theme.shared" "~4.1.0-rc.1"
"@abp/ng.feature-management" "~4.1.0-rc.2"
"@abp/ng.theme.shared" "~4.1.0-rc.2"
tslib "^2.0.0"
"@abp/ng.theme.basic@~4.1.0-rc.1":
version "4.1.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-4.1.0-rc.1.tgz#311e1ee4490803d43b1b1286620f1efa0fe3f459"
integrity sha512-nTf9UaupTxT0UgBx/SwaVuhtutUXFqGGq2dBKI0JNxltgfykLhDXUOTlUnymLlsyu09DJv8OIZbCX6PJAMV1GA==
"@abp/ng.theme.basic@~4.1.0-rc.2":
version "4.1.0-rc.2"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-4.1.0-rc.2.tgz#4c0ccb5386a09cf7d89efaee59c8920265032361"
integrity sha512-2NjHymzE/sgbZ8e+CJ4iMemB+mD6e/VSKA2EkCaekPpXDlp0Z3f2yyhm0jwYqCQPsmCjykdtTpR266yLQOs7ug==
dependencies:
"@abp/ng.theme.shared" "~4.1.0-rc.1"
"@abp/ng.theme.shared" "~4.1.0-rc.2"
tslib "^2.0.0"
"@abp/ng.theme.shared@~4.1.0-rc.1":
version "4.1.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-4.1.0-rc.1.tgz#08c051cafd1364563943f7f8b6d067955227cc7e"
integrity sha512-oNPGXI6gyesDnzHq/x7QuXKr0ISwgz9YaKg2bvzZOCvyZ0usL2P4h/4sQvV9ThCYjDdkVKN+cdW+KNvalJ415w==
"@abp/ng.theme.shared@~4.1.0-rc.2":
version "4.1.0-rc.2"
resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-4.1.0-rc.2.tgz#eaf524ef26f5433ec7ea01d402ba071c42ce1aa6"
integrity sha512-d6dVhV2wer7eeHoT8Y91Bqf7m2r3FVexUp7R+/f93564swuceZGZWSsgntwpHkNdDJaxhLUJ8tG5XMe2deEGTQ==
dependencies:
"@abp/ng.core" "~4.1.0-rc.1"
"@abp/ng.core" "~4.1.0-rc.2"
"@fortawesome/fontawesome-free" "^5.14.0"
"@ng-bootstrap/ng-bootstrap" "^7.0.0"
"@ngx-validate/core" "^0.0.13"
@ -92,13 +92,6 @@
chart.js "^2.9.3"
tslib "^2.0.0"
"@abp/utils@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-4.0.1.tgz#9a4cb420266c49d8084776bd31c86681d4d4d125"
integrity sha512-nI2ZyaNpvohr75p8jeMPWze+hX+mV47EHI+Zloa4GgGmJqrULdG71kYh45R+jGmMTbQL6huaXGnpz4gNF9TGyQ==
dependencies:
just-compare "^1.3.0"
"@abp/utils@^4.1.0-rc.1":
version "4.1.0-rc.1"
resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-4.1.0-rc.1.tgz#2863867985db9d178a5ce4347309f060f1ce6142"
@ -106,6 +99,13 @@
dependencies:
just-compare "^1.3.0"
"@abp/utils@^4.1.0-rc.2":
version "4.1.0-rc.2"
resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-4.1.0-rc.2.tgz#eb6dbf0ee493d0f050b37347d2d6d283098aedae"
integrity sha512-V2k5I89lVBoeGIKgg4p2H9GlMAcDWbctwKZPVwBEMVEVm1uTR2xQAWdTdSFd5Q8I8Xsf/aIG8ELM7l5j2h7/zQ==
dependencies:
just-compare "^1.3.0"
"@angular-builders/jest@^10.0.0":
version "10.0.1"
resolved "https://registry.yarnpkg.com/@angular-builders/jest/-/jest-10.0.1.tgz#a1a6fb5d11b5d54c051bdaa2012b5f046371560c"

@ -5,9 +5,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="4.0.2" />
<PackageReference Include="Volo.Abp.Autofac" Version="4.0.2" />
<PackageReference Include="Volo.Abp.EntityFrameworkCore.SqlServer" Version="4.0.2" />
<ProjectReference Include="..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
<ProjectReference Include="..\..\..\framework\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\..\framework\src\Volo.Abp.EntityFrameworkCore.SqlServer\Volo.Abp.EntityFrameworkCore.SqlServer.csproj" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

Loading…
Cancel
Save