Browse Source

1) More tests

2) Angular2 integrated
pull/1/head
Sebastian 9 years ago
parent
commit
018853cd96
  1. 4
      config/config.js
  2. 2
      src/PinkParrot.Infrastructure/CQRS/Commands/CommandHandler.cs
  3. 10
      src/PinkParrot.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs
  4. 4
      src/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreBus.cs
  5. 7
      src/PinkParrot.Store.MongoDb/Apps/MongoAppRepository.cs
  6. 3
      src/PinkParrot.Store.MongoDb/MongoDbModule.cs
  7. 2
      src/PinkParrot.Store.MongoDb/Schemas/MongoSchemaRepository.cs
  8. 2
      src/PinkParrot.Store.MongoDb/Utils/EntityMapper.cs
  9. 2
      src/PinkParrot.Store.MongoDb/Utils/MongoExtensions.cs
  10. 21
      src/PinkParrot.Write/Apps/AppCommandHandler.cs
  11. 4
      src/PinkParrot/Configurations/EventStoreModule.cs
  12. 7
      src/PinkParrot/Configurations/ReadModule.cs
  13. 33
      src/PinkParrot/Configurations/WebpackUsage.cs
  14. 33
      src/PinkParrot/Modules/Api/Apps/AppController.cs
  15. 15
      src/PinkParrot/Modules/Api/Apps/Models/CreateAppDto.cs
  16. 6
      src/PinkParrot/Modules/Api/EntityCreatedDto.cs
  17. 1
      src/PinkParrot/Modules/Api/Schemas/Models/CreateFieldDto.cs
  18. 3
      src/PinkParrot/Modules/Api/Schemas/SchemasController.cs
  19. 5
      src/PinkParrot/PinkParrot.xproj
  20. 26
      src/PinkParrot/Pipeline/DeactivateForAppDomainAttribute.cs
  21. 129
      src/PinkParrot/Pipeline/WebpackMiddleware.cs
  22. 93
      src/PinkParrot/Pipeline/WebpackRunner.cs
  23. 2
      src/PinkParrot/Properties/launchSettings.json
  24. 26
      src/PinkParrot/Startup.cs
  25. 67
      src/PinkParrot/app-config/auto-loader.js
  26. 47
      src/PinkParrot/app-config/fix-coverage-loader.js
  27. 13
      src/PinkParrot/app-config/helpers.js
  28. 36
      src/PinkParrot/app-config/karma-test-shim.js
  29. 52
      src/PinkParrot/app-config/karma.conf.js
  30. 77
      src/PinkParrot/app-config/karma.coverage.conf.js
  31. 117
      src/PinkParrot/app-config/webpack.common.js
  32. 23
      src/PinkParrot/app-config/webpack.coverage.js
  33. 67
      src/PinkParrot/app-config/webpack.dev.js
  34. 106
      src/PinkParrot/app-config/webpack.prod.js
  35. 77
      src/PinkParrot/app-config/webpack.test.js
  36. 2
      src/PinkParrot/app-libs/typings/global.d.ts
  37. 3
      src/PinkParrot/app/app.component.html
  38. 1
      src/PinkParrot/app/app.component.js.map
  39. 8
      src/PinkParrot/app/app.component.scss
  40. 1
      src/PinkParrot/app/app.component.spec.js.map
  41. 15
      src/PinkParrot/app/app.component.spec.ts
  42. 8
      src/PinkParrot/app/app.component.ts
  43. 1
      src/PinkParrot/app/app.module.js.map
  44. 16
      src/PinkParrot/app/app.module.ts
  45. 1
      src/PinkParrot/app/core/angular/action.js.map
  46. 1
      src/PinkParrot/app/core/angular/action.spec.js.map
  47. 61
      src/PinkParrot/app/core/angular/action.spec.ts
  48. 69
      src/PinkParrot/app/core/angular/action.ts
  49. 1
      src/PinkParrot/app/core/angular/cloak.directive.js.map
  50. 1
      src/PinkParrot/app/core/angular/cloak.directive.spec.js.map
  51. 28
      src/PinkParrot/app/core/angular/cloak.directive.spec.ts
  52. 19
      src/PinkParrot/app/core/angular/cloak.directive.ts
  53. 11
      src/PinkParrot/app/core/angular/color-picker.component.html
  54. 1
      src/PinkParrot/app/core/angular/color-picker.component.js.map
  55. 56
      src/PinkParrot/app/core/angular/color-picker.component.scss
  56. 1
      src/PinkParrot/app/core/angular/color-picker.component.spec.js.map
  57. 124
      src/PinkParrot/app/core/angular/color-picker.component.spec.ts
  58. 96
      src/PinkParrot/app/core/angular/color-picker.component.ts
  59. 1
      src/PinkParrot/app/core/angular/date-time.pipes.js.map
  60. 1
      src/PinkParrot/app/core/angular/date-time.pipes.spec.js.map
  61. 87
      src/PinkParrot/app/core/angular/date-time.pipes.spec.ts
  62. 65
      src/PinkParrot/app/core/angular/date-time.pipes.ts
  63. 1
      src/PinkParrot/app/core/angular/drag-model.directive.js.map
  64. 117
      src/PinkParrot/app/core/angular/drag-model.directive.ts
  65. 1
      src/PinkParrot/app/core/angular/focus-on-change.directive.js.map
  66. 1
      src/PinkParrot/app/core/angular/focus-on-change.directive.spec.js.map
  67. 52
      src/PinkParrot/app/core/angular/focus-on-change.directive.spec.ts
  68. 28
      src/PinkParrot/app/core/angular/focus-on-change.directive.ts
  69. 1
      src/PinkParrot/app/core/angular/image-drop.directive.js.map
  70. 127
      src/PinkParrot/app/core/angular/image-drop.directive.ts
  71. 1
      src/PinkParrot/app/core/angular/money.pipe.js.map
  72. 1
      src/PinkParrot/app/core/angular/money.pipe.spec.js.map
  73. 48
      src/PinkParrot/app/core/angular/money.pipe.spec.ts
  74. 37
      src/PinkParrot/app/core/angular/money.pipe.ts
  75. 1
      src/PinkParrot/app/core/angular/shortcut.component.js.map
  76. 1
      src/PinkParrot/app/core/angular/shortcut.component.spec.js.map
  77. 83
      src/PinkParrot/app/core/angular/shortcut.component.spec.ts
  78. 54
      src/PinkParrot/app/core/angular/shortcut.component.ts
  79. 3
      src/PinkParrot/app/core/angular/slider.component.html
  80. 1
      src/PinkParrot/app/core/angular/slider.component.js.map
  81. 37
      src/PinkParrot/app/core/angular/slider.component.scss
  82. 145
      src/PinkParrot/app/core/angular/slider.component.ts
  83. 1
      src/PinkParrot/app/core/angular/spinner.component.js.map
  84. 40
      src/PinkParrot/app/core/angular/spinner.component.ts
  85. 1
      src/PinkParrot/app/core/angular/user-report.component.js.map
  86. 37
      src/PinkParrot/app/core/angular/user-report.component.ts
  87. 1
      src/PinkParrot/app/core/angular/validators.js.map
  88. 1
      src/PinkParrot/app/core/angular/validators.spec.js.map
  89. 50
      src/PinkParrot/app/core/angular/validators.spec.ts
  90. 26
      src/PinkParrot/app/core/angular/validators.ts
  91. 1
      src/PinkParrot/app/core/configurations.js.map
  92. 31
      src/PinkParrot/app/core/configurations.ts
  93. 1
      src/PinkParrot/app/core/index.js.map
  94. 29
      src/PinkParrot/app/core/index.ts
  95. 1
      src/PinkParrot/app/core/plattform.js.map
  96. 14
      src/PinkParrot/app/core/plattform.ts
  97. 1
      src/PinkParrot/app/core/services/clipboard.service.js.map
  98. 1
      src/PinkParrot/app/core/services/clipboard.service.spec.js.map
  99. 51
      src/PinkParrot/app/core/services/clipboard.service.spec.ts
  100. 34
      src/PinkParrot/app/core/services/clipboard.service.ts

4
config/config.js

@ -0,0 +1,4 @@
fromCategory('pinkparrot')
.whenAny(function(s,e) {
linkTo('pinkparrot',e);
});

2
src/PinkParrot.Infrastructure/CQRS/Commands/CommandHandler.cs

@ -44,7 +44,7 @@ namespace PinkParrot.Infrastructure.CQRS.Commands
await creator(domainObject); await creator(domainObject);
await domainObjectRepository.SaveAsync(domainObject, command.AggregateId); await domainObjectRepository.SaveAsync(domainObject, Guid.NewGuid());
} }
protected Task CreateAsync(IAggregateCommand command, Action<T> creator) protected Task CreateAsync(IAggregateCommand command, Action<T> creator)

10
src/PinkParrot.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs

@ -13,6 +13,7 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore
{ {
public sealed class DefaultNameResolver : IStreamNameResolver public sealed class DefaultNameResolver : IStreamNameResolver
{ {
private const string Suffix = "DomainObject";
private readonly string prefix; private readonly string prefix;
public DefaultNameResolver(string prefix) public DefaultNameResolver(string prefix)
@ -24,7 +25,14 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore
public string GetStreamName(Type aggregateType, Guid id) public string GetStreamName(Type aggregateType, Guid id)
{ {
return string.Format(CultureInfo.InvariantCulture, "{0}-{1}-{2}", prefix, char.ToLower(aggregateType.Name[0]) + aggregateType.Name.Substring(1), id); var typeName = char.ToLower(aggregateType.Name[0]) + aggregateType.Name.Substring(1);
if (typeName.EndsWith(Suffix))
{
typeName = typeName.Substring(0, typeName.Length - Suffix.Length);
}
return string.Format(CultureInfo.InvariantCulture, "{0}-{1}-{2}", prefix, typeName, id);
} }
} }
} }

4
src/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreBus.cs

@ -66,7 +66,7 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore
var position = positions.ReadPosition(); var position = positions.ReadPosition();
logger.LogInformation($"Subscribing from: {0}", position); logger.LogInformation("Subscribing from {0}", position.HasValue ? position.Value.ToString() : "beginning");
var settings = var settings =
new CatchUpSubscriptionSettings( new CatchUpSubscriptionSettings(
@ -97,6 +97,8 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore
var @event = formatter.Parse(resolvedEvent); var @event = formatter.Parse(resolvedEvent);
logger.LogInformation("Received event {0} ({1})", @event.Payload.GetType().Name, @event.Headers.AggregateId());
if (isLive) if (isLive)
{ {
DispatchConsumers(liveConsumers, @event).Wait(); DispatchConsumers(liveConsumers, @event).Wait();

7
src/PinkParrot.Store.MongoDb/Apps/MongoAppRepository.cs

@ -28,6 +28,11 @@ namespace PinkParrot.Store.MongoDb.Apps
{ {
} }
protected override Task SetupCollectionAsync(IMongoCollection<MongoAppEntity> collection)
{
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Name));
}
public async Task<IReadOnlyList<IAppEntity>> QueryAllAsync() public async Task<IReadOnlyList<IAppEntity>> QueryAllAsync()
{ {
var entities = var entities =
@ -46,7 +51,7 @@ namespace PinkParrot.Store.MongoDb.Apps
public Task On(AppCreated @event, EnvelopeHeaders headers) public Task On(AppCreated @event, EnvelopeHeaders headers)
{ {
return Collection.CreateAsync(headers, a => SimpleMapper.Map(a, @event)); return Collection.CreateAsync(headers, a => SimpleMapper.Map(@event, a));
} }
public Task On(Envelope<IEvent> @event) public Task On(Envelope<IEvent> @event)

3
src/PinkParrot.Store.MongoDb/MongoDbModule.cs

@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.MongoDB; using Microsoft.AspNetCore.Identity.MongoDB;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using MongoDB.Driver; using MongoDB.Driver;
using PinkParrot.Infrastructure.CQRS.Events;
using PinkParrot.Infrastructure.CQRS.EventStore; using PinkParrot.Infrastructure.CQRS.EventStore;
using PinkParrot.Read.Apps.Repositories; using PinkParrot.Read.Apps.Repositories;
using PinkParrot.Read.Schemas.Repositories; using PinkParrot.Read.Schemas.Repositories;
@ -59,10 +60,12 @@ namespace PinkParrot.Store.MongoDb
builder.RegisterType<MongoSchemaRepository>() builder.RegisterType<MongoSchemaRepository>()
.As<ISchemaRepository>() .As<ISchemaRepository>()
.As<ICatchEventConsumer>()
.SingleInstance(); .SingleInstance();
builder.RegisterType<MongoAppRepository>() builder.RegisterType<MongoAppRepository>()
.As<IAppRepository>() .As<IAppRepository>()
.As<ICatchEventConsumer>()
.SingleInstance(); .SingleInstance();
} }
} }

2
src/PinkParrot.Store.MongoDb/Schemas/MongoSchemaRepository.cs

@ -132,7 +132,7 @@ namespace PinkParrot.Store.MongoDb.Schemas
public Task On(SchemaCreated @event, EnvelopeHeaders headers) public Task On(SchemaCreated @event, EnvelopeHeaders headers)
{ {
return Collection.CreateAsync(headers, s => SimpleMapper.Map(s, @event)); return Collection.CreateAsync(headers, s => SimpleMapper.Map(@event, s));
} }
public Task On(Envelope<IEvent> @event) public Task On(Envelope<IEvent> @event)

2
src/PinkParrot.Store.MongoDb/Utils/EntityMapper.cs

@ -63,7 +63,7 @@ namespace PinkParrot.Store.MongoDb.Utils
updater(entity); updater(entity);
return collection.InsertOneIfExistsAsync(entity); return collection.InsertOneIfNotExistsAsync(entity);
} }
public static async Task UpdateAsync<T>(this IMongoCollection<T> collection, EnvelopeHeaders headers, Action<T> updater) where T : class, IEntity public static async Task UpdateAsync<T>(this IMongoCollection<T> collection, EnvelopeHeaders headers, Action<T> updater) where T : class, IEntity

2
src/PinkParrot.Store.MongoDb/Utils/MongoExtensions.cs

@ -13,7 +13,7 @@ namespace PinkParrot.Store.MongoDb.Utils
{ {
public static class MongoExtensions public static class MongoExtensions
{ {
public static async Task<bool> InsertOneIfExistsAsync<T>(this IMongoCollection<T> collection, T document) public static async Task<bool> InsertOneIfNotExistsAsync<T>(this IMongoCollection<T> collection, T document)
{ {
try try
{ {

21
src/PinkParrot.Write/Apps/AppCommandHandler.cs

@ -7,24 +7,39 @@
// ========================================================================== // ==========================================================================
using System.Threading.Tasks; using System.Threading.Tasks;
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Infrastructure.CQRS.Commands;
using PinkParrot.Infrastructure.Dispatching; using PinkParrot.Infrastructure.Dispatching;
using PinkParrot.Read.Apps.Repositories;
using PinkParrot.Write.Apps.Commands; using PinkParrot.Write.Apps.Commands;
namespace PinkParrot.Write.Apps namespace PinkParrot.Write.Apps
{ {
public class AppCommandHandler : CommandHandler<AppDomainObject> public class AppCommandHandler : CommandHandler<AppDomainObject>
{ {
private readonly IAppRepository appRepository;
public AppCommandHandler( public AppCommandHandler(
IDomainObjectFactory domainObjectFactory, IDomainObjectFactory domainObjectFactory,
IDomainObjectRepository domainObjectRepository) IDomainObjectRepository domainObjectRepository,
IAppRepository appRepository)
: base(domainObjectFactory, domainObjectRepository) : base(domainObjectFactory, domainObjectRepository)
{ {
Guard.NotNull(appRepository, nameof(appRepository));
this.appRepository = appRepository;
} }
public Task On(CreateApp command) public async Task On(CreateApp command)
{
if (await appRepository.FindAppByNameAsync(command.Name) != null)
{ {
return CreateAsync(command, x => x.Create(command)); var error = new ValidationError($"A app with name '{command.Name}' already exists", "Name");
throw new ValidationException("Cannot create a new app", error);
}
await CreateAsync(command, x => x.Create(command));
} }
public override Task<bool> HandleAsync(CommandContext context) public override Task<bool> HandleAsync(CommandContext context)

4
src/PinkParrot/Configurations/EventStoreModule.cs

@ -50,6 +50,10 @@ namespace PinkParrot.Configurations
return new DefaultNameResolver(options.Prefix); return new DefaultNameResolver(options.Prefix);
}).SingleInstance(); }).SingleInstance();
builder.Register(c => new DefaultNameResolver("pinkparrot"))
.As<IStreamNameResolver>()
.SingleInstance();
} }
} }
} }

7
src/PinkParrot/Configurations/ReadModule.cs

@ -10,10 +10,8 @@ using Autofac;
using PinkParrot.Infrastructure.CQRS.Events; using PinkParrot.Infrastructure.CQRS.Events;
using PinkParrot.Read.Apps.Services; using PinkParrot.Read.Apps.Services;
using PinkParrot.Read.Apps.Services.Implementations; using PinkParrot.Read.Apps.Services.Implementations;
using PinkParrot.Read.Schemas.Repositories;
using PinkParrot.Read.Schemas.Services; using PinkParrot.Read.Schemas.Services;
using PinkParrot.Read.Schemas.Services.Implementations; using PinkParrot.Read.Schemas.Services.Implementations;
using PinkParrot.Store.MongoDb.Schemas;
namespace PinkParrot.Configurations namespace PinkParrot.Configurations
{ {
@ -29,11 +27,6 @@ namespace PinkParrot.Configurations
.As<ISchemaProvider>() .As<ISchemaProvider>()
.As<ILiveEventConsumer>() .As<ILiveEventConsumer>()
.SingleInstance(); .SingleInstance();
builder.RegisterType<MongoSchemaRepository>()
.As<ISchemaRepository>()
.As<ICatchEventConsumer>()
.SingleInstance();
} }
} }
} }

33
src/PinkParrot/Configurations/WebpackUsage.cs

@ -0,0 +1,33 @@
// ==========================================================================
// WebpackUsage.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using PinkParrot.Pipeline;
namespace PinkParrot.Configurations
{
public static class WebpackExtensions
{
public static IServiceCollection AddWebpack(this IServiceCollection services)
{
services.AddSingleton<WebpackRunner>();
return services;
}
public static IApplicationBuilder UseWebpack(this IApplicationBuilder app)
{
app.ApplicationServices.GetService<WebpackRunner>().Execute();
app.UseMiddleware<WebpackMiddleware>();
return app;
}
}
}

33
src/PinkParrot/Modules/Api/Apps/AppController.cs

@ -1,31 +1,54 @@
using System; // ==========================================================================
// AppController.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using PinkParrot.Infrastructure.CQRS.Commands;
using PinkParrot.Infrastructure.Reflection; using PinkParrot.Infrastructure.Reflection;
using PinkParrot.Modules.Api.Apps.Models; using PinkParrot.Modules.Api.Apps.Models;
using PinkParrot.Modules.Api.Schemas.Models; using PinkParrot.Pipeline;
using PinkParrot.Read.Apps.Repositories; using PinkParrot.Read.Apps.Repositories;
using PinkParrot.Write.Apps.Commands;
namespace PinkParrot.Modules.Api.Apps namespace PinkParrot.Modules.Api.Apps
{ {
public class AppController [DeactivateForAppDomain]
public class AppController : ControllerBase
{ {
private readonly IAppRepository appRepository; private readonly IAppRepository appRepository;
public AppController(IAppRepository appRepository) public AppController(ICommandBus commandBus, IAppRepository appRepository)
: base(commandBus)
{ {
this.appRepository = appRepository; this.appRepository = appRepository;
} }
[HttpGet] [HttpGet]
[Route("api/schemas/")] [Route("api/apps/")]
public async Task<List<ListAppDto>> Query() public async Task<List<ListAppDto>> Query()
{ {
var schemas = await appRepository.QueryAllAsync(); var schemas = await appRepository.QueryAllAsync();
return schemas.Select(s => SimpleMapper.Map(s, new ListAppDto())).ToList(); return schemas.Select(s => SimpleMapper.Map(s, new ListAppDto())).ToList();
} }
[HttpPost]
[Route("api/apps/")]
public async Task<IActionResult> Create([FromBody] CreateAppDto model)
{
var command = SimpleMapper.Map(model, new CreateApp { AggregateId = Guid.NewGuid() });
await CommandBus.PublishAsync(command);
return CreatedAtAction("Query", new EntityCreatedDto { Id = command.AggregateId });
}
} }
} }

15
src/PinkParrot/Modules/Api/Apps/Models/CreateAppDto.cs

@ -0,0 +1,15 @@
// ==========================================================================
// CreateAppDto.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
namespace PinkParrot.Modules.Api.Apps.Models
{
public sealed class CreateAppDto
{
public string Name { get; set; }
}
}

6
src/PinkParrot/Modules/Api/EntityCreatedDto.cs

@ -6,14 +6,10 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
namespace PinkParrot.Modules.Api namespace PinkParrot.Modules.Api
{ {
public class EntityCreatedDto public class EntityCreatedDto
{ {
[Required] public object Id { get; set; }
public Guid Id { get; set; }
} }
} }

1
src/PinkParrot/Modules/Api/Schemas/Models/CreateFieldDto.cs

@ -6,7 +6,6 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;

3
src/PinkParrot/Modules/Api/Schemas/SchemasController.cs

@ -11,7 +11,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using PinkParrot.Core.Schemas;
using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Infrastructure.CQRS.Commands;
using PinkParrot.Infrastructure.Reflection; using PinkParrot.Infrastructure.Reflection;
using PinkParrot.Modules.Api.Schemas.Models; using PinkParrot.Modules.Api.Schemas.Models;
@ -64,7 +63,7 @@ namespace PinkParrot.Modules.Api.Schemas
await CommandBus.PublishAsync(command); await CommandBus.PublishAsync(command);
return CreatedAtAction("Query", new EntityCreatedDto { Id = command.AggregateId }); return CreatedAtAction("Get", new { name = model.Name }, new EntityCreatedDto { Id = command.Name });
} }
[HttpPut] [HttpPut]

5
src/PinkParrot/PinkParrot.xproj

@ -4,7 +4,6 @@
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<ProjectGuid>61f6bbce-a080-4400-b194-70e2f5d2096e</ProjectGuid> <ProjectGuid>61f6bbce-a080-4400-b194-70e2f5d2096e</ProjectGuid>
@ -13,10 +12,12 @@
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<SchemaVersion>2.0</SchemaVersion> <SchemaVersion>2.0</SchemaVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<TypeScriptCompileBlocked>True</TypeScriptCompileBlocked>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<DnxInvisibleContent Include="bower.json" /> <DnxInvisibleContent Include="bower.json" />
<DnxInvisibleContent Include=".bowerrc" /> <DnxInvisibleContent Include=".bowerrc" />

26
src/PinkParrot/Pipeline/DeactivateForAppDomainAttribute.cs

@ -0,0 +1,26 @@
// ==========================================================================
// DeactivateForAppDomainAttribute.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace PinkParrot.Pipeline
{
public sealed class DeactivateForAppDomainAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var app = context.HttpContext.Features.Get<IAppFeature>();
if (app != null)
{
context.Result = new NotFoundResult();
}
}
}
}

129
src/PinkParrot/Pipeline/WebpackMiddleware.cs

@ -0,0 +1,129 @@
// ==========================================================================
// WebpackMiddleware.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
// ReSharper disable LoopCanBeConvertedToQuery
namespace PinkParrot.Pipeline
{
public sealed class WebpackMiddleware
{
private const string Host = "localhost";
private const string Port = "3000";
private static readonly string[] Scripts = { "polyfills.js", "vendor.js", "app.js" };
private static readonly string[] Styles = { "vendor.css" };
private readonly RequestDelegate next;
private readonly ILogger logger;
public WebpackMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<WebpackMiddleware>();
this.next = next;
}
public async Task Invoke(HttpContext context)
{
var buffer = new MemoryStream();
var body = context.Response.Body;
context.Response.Body = buffer;
await next(context);
buffer.Seek(0, SeekOrigin.Begin);
if (context.Response.StatusCode == 200 && IsHtml(context))
{
using (var reader = new StreamReader(buffer))
{
var response = await reader.ReadToEndAsync();
response = InjectStyles(response);
response = InjectScripts(response);
using (var memoryStream = new MemoryStream())
{
using (var writer = new StreamWriter(memoryStream))
{
writer.Write(response);
writer.Flush();
memoryStream.Seek(0, SeekOrigin.Begin);
context.Response.Headers["Content-Length"] = memoryStream.Length.ToString();
await memoryStream.CopyToAsync(body);
}
}
}
}
else
{
await buffer.CopyToAsync(body);
}
context.Response.Body = body;
}
private string InjectStyles(string response)
{
if (!response.Contains("</head>"))
{
return response;
}
logger.LogInformation("A full html page is returned so the necessary styles for webpack will be injected");
var stylesTag = string.Empty;
foreach (var file in Styles)
{
stylesTag += $"<link href=\"http://{Host}:{Port}/{file}\" rel=\"stylesheet\" >";
}
response = response.Replace("</head>", $"{stylesTag}</head>");
logger.LogInformation($"Inject style {stylesTag} as a last element in the head ");
return response;
}
private string InjectScripts(string response)
{
if (!response.Contains("</body>"))
{
return response;
}
logger.LogInformation("A full html page is returned so the necessary script for webpack will be injected");
var scriptsTag = string.Empty;
foreach (var file in Scripts)
{
scriptsTag += $"<script type=\"text/javascript\" src=\"http://{Host}:{Port}/{file}\"></script>";
}
response = response.Replace("</body>", $"{scriptsTag}</body>");
logger.LogInformation($"Inject script {scriptsTag} as a last element in the body ");
return response;
}
private static bool IsHtml(HttpContext context)
{
return context.Response.ContentType?.ToLower().Contains("text/html") == true;
}
}
}

93
src/PinkParrot/Pipeline/WebpackRunner.cs

@ -0,0 +1,93 @@
// ==========================================================================
// WebpackRunner.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System;
using System.Diagnostics;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
// ReSharper disable ConvertToConstant.Local
namespace PinkParrot.Pipeline
{
public sealed class WebpackRunner
{
private const string WebpackDevServer = "webpack-dev-server";
private readonly ILoggerFactory loggerFactory;
private readonly IApplicationLifetime lifetime;
private Process process;
public WebpackRunner(ILoggerFactory loggerFactory, IApplicationLifetime lifetime)
{
this.loggerFactory = loggerFactory;
this.lifetime = lifetime;
}
public void Execute()
{
if (process != null)
{
return;
}
var logger = loggerFactory.CreateLogger(WebpackDevServer);
EnsuereNodeModluesInstalled(logger);
logger.LogInformation($"{WebpackDevServer} Execution started");
var app = GetNodeExecutable(WebpackDevServer);
var args = "--inline --hot --port 3000";
process = Process.Start(new ProcessStartInfo
{
FileName = app, Arguments = args, UseShellExecute = false
});
lifetime.ApplicationStopping.Register(OnShutdown);
logger.LogInformation($"{WebpackDevServer} started successfully");
}
private void OnShutdown()
{
process?.Kill();
process = null;
}
private static void EnsuereNodeModluesInstalled(ILogger logger)
{
logger.LogInformation("Verifying required tools are installed");
if (!File.Exists(GetNodeExecutable(WebpackDevServer)))
{
logger.LogError("webpack-dev-server is not installed. Please install it by executing npm i webpack-dev-server");
}
logger.LogInformation("All node modules are properly installed");
}
private static string GetNodeExecutable(string module)
{
var executablePath = Path.Combine(Directory.GetCurrentDirectory(), "node_modules", ".bin", module);
var osEnVariable = Environment.GetEnvironmentVariable("OS");
if (!string.IsNullOrEmpty(osEnVariable) &&
string.Equals(osEnVariable, "Windows_NT", StringComparison.OrdinalIgnoreCase))
{
executablePath += ".cmd";
}
return executablePath;
}
}
}

2
src/PinkParrot/Properties/launchSettings.json

@ -3,7 +3,7 @@
"windowsAuthentication": false, "windowsAuthentication": false,
"anonymousAuthentication": true, "anonymousAuthentication": true,
"iisExpress": { "iisExpress": {
"applicationUrl": "http://localhost:65351/", "applicationUrl": "http://localhost:5000/",
"sslPort": 0 "sslPort": 0
} }
}, },

26
src/PinkParrot/Startup.cs

@ -7,6 +7,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using Autofac; using Autofac;
using Autofac.Extensions.DependencyInjection; using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@ -39,20 +40,24 @@ namespace PinkParrot
public IServiceProvider ConfigureServices(IServiceCollection services) public IServiceProvider ConfigureServices(IServiceCollection services)
{ {
services.AddMvc().AddAppSerializers();
services.AddRouting();
services.AddMemoryCache();
services.AddOptions();
services.AddEventFormatter(); services.AddEventFormatter();
services.AddIdentity<IdentityUser, IdentityRole>().AddDefaultTokenProviders(); services.AddIdentity<IdentityUser, IdentityRole>().AddDefaultTokenProviders();
services.AddLogging();
services.AddMemoryCache();
services.AddMvc().AddAppSerializers();
services.AddOptions();
services.AddRouting();
services.AddWebpack();
services.Configure<MongoDbModule>( services.Configure<MongoDbOptions>(
Configuration.GetSection("MongoDb")); Configuration.GetSection("stores:mongoDb"));
services.Configure<EventStoreOptions>( services.Configure<EventStoreOptions>(
Configuration.GetSection("EventStore")); Configuration.GetSection("stores:eventStore"));
var builder = new ContainerBuilder(); var builder = new ContainerBuilder();
builder.RegisterModule<InfrastructureModule>(); builder.RegisterModule<InfrastructureModule>();
builder.RegisterModule<EventStoreModule>();
builder.RegisterModule<MongoDbModule>();
builder.RegisterModule<ReadModule>(); builder.RegisterModule<ReadModule>();
builder.RegisterModule<WriteModule>(); builder.RegisterModule<WriteModule>();
builder.Populate(services); builder.Populate(services);
@ -63,10 +68,17 @@ namespace PinkParrot
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{ {
loggerFactory.AddConsole(); loggerFactory.AddConsole();
loggerFactory.AddDebug();
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage();
app.UseWebpack();
app.UseDefaultFiles();
}
else
{
app.UseDefaultFiles(new DefaultFilesOptions { DefaultFileNames = new List<string> { "build/index.html" } });
} }
app.UseApps(); app.UseApps();

67
src/PinkParrot/app-config/auto-loader.js

@ -0,0 +1,67 @@
'use strict';
var path = require('path'),
fs = require('fs'),
loaderUtils = require('loader-utils'),
SourceMap = require('source-map');
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
};
function applyPlaceholders(str, dirname, filename) {
if (!str.length) {
return str;
}
return str
.split('[file]').join(filename)
.split('[File]').join(capitalize(filename));
};
function loadBaggage(source, sourcemap) {
var query = loaderUtils.parseQuery(this.query);
var srcFilepath = this.resourcePath;
var srcFilename = path.basename(srcFilepath, path.extname(srcFilepath));
var srcDirpath = path.dirname(srcFilepath);
var srcDirname = srcDirpath.split(path.sep).pop();
this.cacheable();
if (Object.keys(query).length) {
var inject = '\n/* injects from baggage-loader */\n';
Object.keys(query).forEach(function (baggageFile) {
var baggageVar = query[baggageFile];
if (typeof baggageVar === 'string' || baggageVar === true) {
baggageFile = applyPlaceholders(baggageFile, srcDirname, srcFilename);
try {
var stats = fs.statSync(path.resolve(srcDirpath, baggageFile));
if (stats.isFile()) {
if (baggageVar.length) {
inject += 'const ' + baggageVar + ' = ';
}
if (baggageVar === 'styles') {
inject += '[require(\'./' + baggageFile + '\')];\n';
} else {
inject += 'require(\'./' + baggageFile + '\');\n';
}
}
} catch (e) { }
}
});
inject += '\n';
return inject + source;
}
return source;
};
module.exports = loadBaggage;

47
src/PinkParrot/app-config/fix-coverage-loader.js

@ -0,0 +1,47 @@
function fixCoverage(contents) {
this.cacheable();
var ignores = [
{ name: 'arguments', line: 'var _a' },
{ name: 'decorate', line: 'var __decorate =', },
{ name: 'metadata', line: 'var __metadata =', },
{ name: 'extends', line: 'var __extends =', },
{ name: 'export', line: 'function __export' }
];
var updates = 0;
var rows = contents.split('\n');
for (var rowIndex = 0; rowIndex < rows.length; rowIndex++) {
var row = rows[rowIndex].trim();
for (var ignoreIndex = 0; ignoreIndex < ignores.length; ignoreIndex++) {
var ignore = ignores[ignoreIndex];
if (row.indexOf(ignore.line) >= 0) {
rows.splice(rowIndex, 0, '/* istanbul ignore next: TypeScript ' + ignore.name + ' */');
rowIndex++;
updates++;
break;
}
}
if (row.indexOf('hasOwnProperty') >= 0) {
rows.splice(rowIndex, 0, '/* istanbul ignore else */');
rowIndex++;
updates++;
}
if (updates === ignores.length) {
break;
}
}
if (updates > 0) {
return rows.join('\n');
} else {
return contents;
}
}
module.exports = fixCoverage;

13
src/PinkParrot/app-config/helpers.js

@ -0,0 +1,13 @@
// ReSharper disable InconsistentNaming
// ReSharper disable PossiblyUnassignedProperty
// ReSharper disable InconsistentNaming
var path = require('path');
var appRoot = path.resolve(__dirname, '..');
exports.root = function () {
var newArgs = Array.prototype.slice.call(arguments, 0);
return path.join.apply(path, [appRoot].concat(newArgs));
};

36
src/PinkParrot/app-config/karma-test-shim.js

@ -0,0 +1,36 @@
Error.stackTraceLimit = Infinity;
require('core-js/es6');
require('core-js/es7/reflect');
require('zone.js/dist/zone');
require('zone.js/dist/long-stack-trace-zone');
require('zone.js/dist/proxy');
require('zone.js/dist/sync-test');
require('zone.js/dist/jasmine-patch');
require('zone.js/dist/async-test');
require('zone.js/dist/fake-async-test');
require('rxjs/Rx');
var testing = require('@angular/core/testing');
var browser = require('@angular/platform-browser-dynamic/testing');
testing.TestBed.initTestEnvironment(
browser.BrowserDynamicTestingModule,
browser.platformBrowserDynamicTesting()
);
var testContext = require.context('../app', true, /\.spec\.ts/);
/*
* get all the files, for each file, call the context function
* that will require the file and load it up here. Context will
* loop and require those spec files here
*/
function requireAll(requireContext) {
return requireContext.keys().map(requireContext);
}
// requires and returns all modules that match
var modules = requireAll(testContext);

52
src/PinkParrot/app-config/karma.conf.js

@ -0,0 +1,52 @@
var webpackConfig = require('./webpack.test');
module.exports = function (config) {
var _config = {
/**
* Base path that will be used to resolve all patterns (e.g. files, exclude)
*/
basePath: '',
frameworks: ['jasmine'],
/**
* Load additional test shim to setup angular2 for testing
*/
files: [
{ pattern: './app-config/karma-test-shim.js', watched: false }
],
preprocessors: {
'./app-config/karma-test-shim.js': ['webpack', 'sourcemap'],
},
/**
* Load the files with webpack and use test configuration for it.
*/
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-only'
},
webpackServer: {
noInfo: true
},
/*
* Use a mocha style console reporter, html reporter and the code coverage reporter
*/
reporters: ['mocha'],
/**
* Run with chrome to enable debugging
*
* available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
*/
browsers: ['Chrome']
};
config.set(_config);
};

77
src/PinkParrot/app-config/karma.coverage.conf.js

@ -0,0 +1,77 @@
var webpackConfig = require('./webpack.coverage');
module.exports = function (config) {
var _config = {
/**
* Base path that will be used to resolve all patterns (e.g. files, exclude)
*/
basePath: '',
frameworks: ['jasmine'],
/**
* Load additional test shim to setup angular2 for testing
*/
files: [
{ pattern: './app-config/karma-test-shim.js', watched: false }
],
preprocessors: {
'./config/karma-test-shim.js': ['webpack', 'sourcemap'],
},
/**
* Load the files with webpack and use test configuration for it.
*/
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-only'
},
webpackServer: {
noInfo: true
},
/*
* Use a mocha style console reporter, html reporter and the code coverage reporter
*/
reporters: ['mocha', 'html', 'coverage'],
// HtmlReporter configuration
htmlReporter: {
useCompactStyle: true,
/**
* Use the same folder like the html report for coverage reports
*/
outputFile: '_test-output/tests.html',
/**
* Group the output by test suite (describe), equivalent to mocha reporter
*/
groupSuites: true
},
coverageReporter: {
type: 'html',
/**
* Use the same folder like the html report for coverage reports
*/
dir: '_test-output/coverage'
},
/**
* Disable continuous Integration mode, run only one time
*/
singleRun: true,
/**
* Run with chrome because phantom js does not provide all types, e.g. DragEvent
*
* available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
*/
browsers: ['PhantomJS']
};
config.set(_config);
};

117
src/PinkParrot/app-config/webpack.common.js

@ -0,0 +1,117 @@
// ReSharper disable InconsistentNaming
// ReSharper disable PossiblyUnassignedProperty
var webpack = require('webpack'),
path = require('path'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
ExtractTextPlugin = require('extract-text-webpack-plugin'),
helpers = require('./helpers');
module.exports = {
/**
* The entry point for the bundle
* Our Angular.js app
*
* See: http://webpack.github.io/docs/configuration.html#entry
*/
entry: {
'polyfills': './app/polyfills.ts',
'vendor': './app/vendor.ts',
'app': './app/main.ts'
},
/**
* Options affecting the resolving of modules.
*
* See: http://webpack.github.io/docs/configuration.html#resolve
*/
resolve: {
/**
* An array of extensions that should be used to resolve modules.
*
* See: http://webpack.github.io/docs/configuration.html#resolve-extensions
*/
extensions: ['', '.js', '.ts', '.css', '.scss'],
root: [
helpers.root('app'),
helpers.root('app-libs')
]
},
/*
* Options affecting the normal modules.
*
* See: http://webpack.github.io/docs/configuration.html#module
*/
module: {
preLoaders: [{
test: /\.ts/,
loader: helpers.root('app-config', 'auto-loader') + '?[file].html=template&[file].scss=styles',
}],
/**
* An array of automatically applied loaders.
*
* IMPORTANT: The loaders here are resolved relative to the resource which they are applied to.
* This means they are not resolved relative to the configuration file.
*
* See: http://webpack.github.io/docs/configuration.html#module-loaders
*/
loaders: [
{
test: /\.ts$/,
loader: 'awesome-typescript-loader'
}, {
test: /\.html$/,
loader: 'html'
}, {
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)(\?.*$|$)/,
loader: 'file?name=assets/[name].[hash].[ext]'
}, {
test: /\.css$/,
loader: ExtractTextPlugin.extract('style', 'css?sourceMap')
}, {
test: /\.scss$/,
exclude: helpers.root('app', 'theme'),
loaders: ['raw', 'sass']
}
]
},
plugins: [
/**
* Plugin: CommonsChunkPlugin
* Description: Shares common code between the pages.
* It identifies common modules and put them into a commons chunk.
*
* See: https://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin
*/
new webpack.optimize.CommonsChunkPlugin({
name: ['app', 'vendor', 'polyfills']
}),
/**
* Plugin: HtmlWebpackPlugin
* Description: Simplifies creation of HTML files to serve your webpack bundles.
* This is especially useful for webpack bundles that include a hash in the filename
* which changes every compilation.
*
* See: https://github.com/ampedandwired/html-webpack-plugin
*/
new HtmlWebpackPlugin({
template: 'wwwroot/index.html'
}),
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
/**
* Shim additional libraries
*
* See: https://webpack.github.io/docs/shimming-modules.html
*/
new webpack.ProvidePlugin({
// Mouse trap handles shortcut management
'Mousetrap': 'mousetrap/mousetrap'
})
]
};

23
src/PinkParrot/app-config/webpack.coverage.js

@ -0,0 +1,23 @@

var webpackMerge = require('webpack-merge'),
path = require('path'),
helpers = require('./helpers'),
testConfig = require('./webpack.test.js');
module.exports = webpackMerge(testConfig, {
module: {
postLoaders: [
{
test: /\.(js|ts)$/,
include: helpers.root('app'),
exclude: [/\.(e2e|spec)\.ts$/],
loader: 'istanbul-instrumenter-loader'
}, {
test: /\.(js|ts)$/,
include: helpers.root('app'),
exclude: [/\.(e2e|spec)\.ts$/],
loader: helpers.root('app-config', 'fix-coverage-loader')
}
]
}
});

67
src/PinkParrot/app-config/webpack.dev.js

@ -0,0 +1,67 @@
// ReSharper disable InconsistentNaming
// ReSharper disable PossiblyUnassignedProperty
var webpackMerge = require('webpack-merge'),
ExtractTextPlugin = require('extract-text-webpack-plugin'),
commonConfig = require('./webpack.common.js'),
helpers = require('./helpers');
module.exports = webpackMerge(commonConfig, {
/**
* Developer tool to enhance debugging
*
* See: http://webpack.github.io/docs/configuration.html#devtool
* See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps
*/
devtool: 'cheap-module-eval-source-map',
output: {
filename: '[name].js',
// Set the public path, because we are running the website from another port (5000)
publicPath: 'http://localhost:3000/'
},
/*
* Options affecting the normal modules.
*
* See: http://webpack.github.io/docs/configuration.html#module
*/
module: {
/**
* An array of automatically applied loaders.
*
* IMPORTANT: The loaders here are resolved relative to the resource which they are applied to.
* This means they are not resolved relative to the configuration file.
*
* See: http://webpack.github.io/docs/configuration.html#module-loaders
*/
loaders: [
{
test: /\.scss$/,
include: helpers.root('app', 'theme'),
loaders: ['style', 'css', 'sass?sourceMap']
}
]
},
plugins: [
new ExtractTextPlugin('[name].css')
],
tslint: {
/**
* Run tslint in production build, but do not fail if there is a warning.
*
* See: https://github.com/wbuchwalter/tslint-loader
*/
failOnHint: false,
/**
* Share the configuration file with the IDE
*/
configuration: require('./../tslint.json')
},
devServer: {
historyApiFallback: true, stats: 'minimal'
}
});

106
src/PinkParrot/app-config/webpack.prod.js

@ -0,0 +1,106 @@
 var webpack = require('webpack'),
webpackMerge = require('webpack-merge'),
ExtractTextPlugin = require('extract-text-webpack-plugin'),
commonConfig = require('./webpack.common.js'),
helpers = require('./helpers');
const ENV = process.env.NODE_ENV = process.env.ENV = 'production';
module.exports = webpackMerge(commonConfig, {
devtool: 'source-map',
output: {
/**
* The output directory as absolute path (required).
*
* See: http://webpack.github.io/docs/configuration.html#output-path
*/
path: helpers.root('wwwroot/build/'),
publicPath: '/build/',
/**
* Specifies the name of each output file on disk.
* IMPORTANT: You must not specify an absolute path here!
*
* See: http://webpack.github.io/docs/configuration.html#output-filename
*/
filename: '[name].[hash].js',
/**
* The filename of non-entry chunks as relative path
* inside the output.path directory.
*
* See: http://webpack.github.io/docs/configuration.html#output-chunkfilename
*/
chunkFilename: '[id].[hash].chunk.js'
},
/*
* Options affecting the normal modules.
*
* See: http://webpack.github.io/docs/configuration.html#module
*/
module: {
preLoaders: [{
test: /\.ts$/,
loader: "tslint"
}],
/**
* An array of automatically applied loaders.
*
* IMPORTANT: The loaders here are resolved relative to the resource which they are applied to.
* This means they are not resolved relative to the configuration file.
*
* See: http://webpack.github.io/docs/configuration.html#module-loaders
*/
loaders: [
{
test: /\.scss$/,
include: helpers.root('app', 'theme'),
loader: ExtractTextPlugin.extract('style', 'css!sass?sourceMap')
}
]
},
tslint: {
/**
* Run tslint in production build and fail if there is one warning.
*
* See: https://github.com/wbuchwalter/tslint-loader
*/
failOnHint: true,
/**
* Share the configuration file with the IDE
*/
configuration: require('./../tslint.json')
},
/**
* Html loader advanced options
*
* See: https://github.com/webpack/html-loader#advanced-options
*/
htmlLoader: {
minimize: false
},
plugins: [
new webpack.NoErrorsPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({ mangle: { screw_ie8: true, keep_fnames: true } }),
new webpack.DefinePlugin({ 'process.env': { 'ENV': JSON.stringify(ENV) } }),
new ExtractTextPlugin('[name].[hash].css'),
function () {
this.plugin("done", function (stats) {
if (stats.compilation.errors && stats.compilation.errors.length && process.argv.indexOf('--watch') == -1) {
console.log(stats.compilation.errors);
throw new Error('webpack build failed.');
}
});
}
]
});

77
src/PinkParrot/app-config/webpack.test.js

@ -0,0 +1,77 @@
var webpack = require('webpack'),
helpers = require('./helpers');
module.exports = {
/**
* Source map for Karma from the help of karma-sourcemap-loader & karma-webpack
*
* Do not change, leave as is or it wont work.
* See: https://github.com/webpack/karma-webpack#source-maps
*/
devtool: 'inline-source-map',
resolve: {
/**
* An array of extensions that should be used to resolve modules.
*
* See: http://webpack.github.io/docs/configuration.html#resolve-extensions
*/
extensions: ['', '.ts', '.js'],
root: [
helpers.root('app'),
helpers.root('app-libs')
]
},
/*
* Options affecting the normal modules.
*
* See: http://webpack.github.io/docs/configuration.html#module
*/
module: {
preLoaders: [{
test: /\.ts/,
loader: helpers.root('app-config', 'auto-loader') + '?[file].html=template&[file].scss=styles',
}],
/**
* An array of automatically applied loaders.
*
* IMPORTANT: The loaders here are resolved relative to the resource which they are applied to.
* This means they are not resolved relative to the configuration file.
*
* See: http://webpack.github.io/docs/configuration.html#module-loaders
*/
loaders: [
{
test: /\.ts$/,
loader: 'awesome-typescript-loader'
}, {
test: /\.html$/,
loader: 'html'
}, {
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)(\?.*$|$)/,
loader: 'null'
}, {
test: /\.css$/,
loader: 'null'
}, {
test: /\.scss$/,
exclude: helpers.root('app', 'theme'),
loaders: ['raw', 'sass']
}
]
},
plugins: [
/**
* Shim additional libraries
*
* See: https://webpack.github.io/docs/shimming-modules.html
*/
new webpack.ProvidePlugin({
// Mouse trap handles shortcut management
'Mousetrap': 'mousetrap/mousetrap'
})
]
}

2
src/PinkParrot/app-libs/typings/global.d.ts

@ -0,0 +1,2 @@
declare var styles: string[];
declare var template: string;

3
src/PinkParrot/app/app.component.html

@ -0,0 +1,3 @@
<main>
<h1>Hello from Angular 2 App with Webpack!</h1>
</main>

1
src/PinkParrot/app/app.component.js.map

@ -0,0 +1 @@
{"version":3,"file":"app.component.js","sourceRoot":"","sources":["app.component.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,IAAY,GAAG,WAAM,eAAe,CAAC,CAAA;AAOrC;IAAA;IAA4B,CAAC;IAL7B;QAAC,GAAG,CAAC,SAAS,CAAC;YACX,QAAQ,EAAE,QAAQ;YAClB,UAAA,QAAQ;YACR,QAAA,MAAM;SACT,CAAC;;oBAAA;IAC0B,mBAAC;AAAD,CAAC,AAA7B,IAA6B;AAAhB,oBAAY,eAAI,CAAA"}

8
src/PinkParrot/app/app.component.scss

@ -0,0 +1,8 @@
main {
padding: 1em;
font-family: Arial, Helvetica, sans-serif;
font-size: 1.1rem;
text-align: center;
margin-top: 50px;
display: block;
}

1
src/PinkParrot/app/app.component.spec.js.map

@ -0,0 +1 @@
{"version":3,"file":"app.component.spec.js","sourceRoot":"","sources":["app.component.spec.ts"],"names":[],"mappings":";AAAA,wBAAwB,uBAAuB,CAAC,CAAA;AAEhD,8BAA6B,iBAAiB,CAAC,CAAA;AAE/C,QAAQ,CAAC,KAAK,EAAE;IACZ,UAAU,CAAC;QACP,iBAAO,CAAC,sBAAsB,CAAC,EAAE,YAAY,EAAE,CAAC,4BAAY,CAAC,EAAE,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,aAAa,EAAE;QACd,IAAM,OAAO,GAAG,iBAAO,CAAC,eAAe,CAAC,4BAAY,CAAC,CAAC;QAEtD,MAAM,CAAC,OAAO,CAAC,iBAAiB,YAAY,4BAAY,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,4BAA4B,CAAC,CAAC;IACvG,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}

15
src/PinkParrot/app/app.component.spec.ts

@ -0,0 +1,15 @@
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('App', () => {
beforeEach(() => {
TestBed.configureTestingModule({ declarations: [AppComponent] });
});
it('should work', () => {
const fixture = TestBed.createComponent(AppComponent);
expect(fixture.componentInstance instanceof AppComponent).toBe(true, 'should create AppComponent');
});
});

8
src/PinkParrot/app/app.component.ts

@ -0,0 +1,8 @@
import * as Ng2 from '@angular/core';
@Ng2.Component({
selector: 'my-app',
template,
styles
})
export class AppComponent { }

1
src/PinkParrot/app/app.module.js.map

@ -0,0 +1 @@
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["app.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,IAAY,GAAG,WAAM,eAAe,CAAC,CAAA;AAErC,iCAA8B,2BAA2B,CAAC,CAAA;AAE1D,8BAA6B,iBAAiB,CAAC,CAAA;AAW/C;IAAA;IAAyB,CAAC;IAT1B;QAAC,GAAG,CAAC,QAAQ,CAAC;YACV,OAAO,EAAE;gBACL,gCAAa;aAChB;YACD,YAAY,EAAE;gBACV,4BAAY;aACf;YACD,SAAS,EAAE,CAAC,4BAAY,CAAC;SAC5B,CAAC;;iBAAA;IACuB,gBAAC;AAAD,CAAC,AAA1B,IAA0B;AAAb,iBAAS,YAAI,CAAA"}

16
src/PinkParrot/app/app.module.ts

@ -0,0 +1,16 @@
import * as Ng2 from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@Ng2.NgModule({
imports: [
BrowserModule
],
declarations: [
AppComponent
],
bootstrap: [AppComponent]
})
export class AppModule { }

1
src/PinkParrot/app/core/angular/action.js.map

@ -0,0 +1 @@
{"version":3,"file":"action.js","sourceRoot":"","sources":["action.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;AAEH,IAAM,UAAU,GAAG,cAAQ,CAAC,CAAC;AAE7B;IACI,MAAM,CAAC,UAAU,MAAW,EAAE,GAAW;QACrC,IAAI,UAAe,CAAC;QACpB,IAAI,QAAa,CAAC;QAClB,IAAI,aAAkB,CAAC;QACvB,IAAI,YAAiB,CAAC;QAEtB;YACI,IAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;YAE7B,EAAE,CAAC,CAAC,KAAK,IAAI,UAAU,IAAI,UAAU,CAAC,SAAS,IAAI,OAAO,UAAU,CAAC,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC;gBAC5F,YAAY,GAAG,UAAU,CAAC,SAAS,CAAC,UAAC,CAAM,IAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;oBAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAE/E,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACrC,CAAC;QACL,CAAC;QAAA,CAAC;QAEF;YACI,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;gBACf,YAAY,CAAC,WAAW,EAAE,CAAC;gBAE3B,aAAa,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;YAC9D,CAAC;QACL,CAAC;QAAA,CAAC;QAEF,EAAE,CAAC,CAAC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE;gBAC/B,GAAG,EAAE;oBACD,MAAM,CAAC,UAAU,CAAC;gBACtB,CAAC;gBACD,GAAG,EAAE,UAAU,CAAC;oBACZ,QAAQ,GAAG,IAAI,CAAC;oBAEhB,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC;wBAC7B,QAAQ,CAAC,gBAAgB,GAAG,EAAE,CAAC;wBAE/B,IAAI,SAAO,GAAG,QAAQ,CAAC,WAAW,GAAG,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,UAAU,CAAC;wBAEtF,QAAQ,CAAC,WAAW,GAAG;4BACnB,GAAG,CAAC,CAAU,UAAa,EAAb,+BAAa,EAAb,2BAAa,EAAb,IAAa,CAAC;gCAAvB,IAAI,CAAC,sBAAA;gCACN,CAAC,CAAC,WAAW,EAAE,CAAC;6BACnB;4BAED,QAAQ,CAAC,gBAAgB,GAAG,IAAI,CAAC;4BAEjC,SAAO,EAAE,CAAC;wBACd,CAAC,CAAC;oBACN,CAAC;oBAED,aAAa,GAAG,QAAQ,CAAC,gBAAgB,CAAC;oBAE1C,UAAU,GAAG,CAAC,CAAC;oBAEf,WAAW,EAAE,CAAC;oBACd,SAAS,EAAE,CAAC;gBAChB,CAAC;aACJ,CAAC,CAAC;QACP,CAAC;IACL,CAAC,CAAC;AACN,CAAC;AA3De,cAAM,SA2DrB,CAAA"}

1
src/PinkParrot/app/core/angular/action.spec.js.map

@ -0,0 +1 @@
{"version":3,"file":"action.spec.js","sourceRoot":"","sources":["action.spec.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;;;;;;;;;;AAEH,qBAAwB,MAAM,CAAC,CAAA;AAE/B,iBAAuB,OAAO,CAAC,CAAA;AAE/B;IACI,sBAAoB,QAAc,EAAE,GAAG;QAA3B,wBAAsB,GAAtB,gBAAsB;QAAd,aAAQ,GAAR,QAAQ,CAAM;QAE3B,oBAAe,GAAG,KAAK,CAAC;QAGxB,WAAM,GAAG,IAAI,cAAO,EAAU,CAAC,GAAG,CAAC,UAAA,CAAC,IAAM,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAG7E,WAAM,GAAG,IAAI,cAAO,EAAU,CAAC,GAAG,CAAC,UAAA,CAAC,IAAM,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IARzC,CAAC;IAUrC,2BAAI,GAAX;QACI,IAAI,CAAC,MAAM,GAAG,IAAI,cAAO,EAAU,CAAC,GAAG,CAAC,UAAA,CAAC,IAAM,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACtF,CAAC;IAEM,kCAAW,GAAlB;QACI,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;IAChC,CAAC;IAZD;QAAC,SAAM,EAAE;;gDAAA;IAGT;QAAC,SAAM,EAAE;;gDAAA;IAUb,mBAAC;AAAD,CAAC,AAlBD,IAkBC;AAED,QAAQ,CAAC,QAAQ,EAAE;IACf,EAAE,CAAC,+EAA+E,EAAE;QAChF,IAAI,aAAa,GAAG,CAAC,CAAC;QAEtB,IAAM,KAAK,GAAG;YACV,IAAI,EAAE,UAAC,CAAM;gBACT,aAAa,EAAE,CAAC;gBAEhB,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YACvC,CAAC;SACJ,CAAC;QAEF,IAAM,IAAI,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;QAErC,IAAI,CAAC,IAAI,EAAE,CAAC;QAEN,IAAI,CAAC,MAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEhC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAE9B,IAAI,CAAC,WAAW,EAAE,CAAC;QAEb,IAAI,CAAC,MAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEhC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,UAAU,EAAE,CAAC;IAC9C,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}

61
src/PinkParrot/app/core/angular/action.spec.ts

@ -0,0 +1,61 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Subject } from 'rxjs';
import { Action } from './../';
class MockupObject {
constructor(private readonly store: any) { }
public isDestroyCalled = false;
@Action()
public event1 = new Subject<string>().map(x => { return { type: 'MOCK_ACTION' }; });
@Action()
public event2 = new Subject<string>().map(x => { return { type: 'MOCK_ACTION' }; });
public init() {
this.event2 = new Subject<string>().map(x => { return { type: 'MOCK_ACTION' }; });
}
public ngOnDestroy() {
this.isDestroyCalled = true;
}
}
describe('Action', () => {
it('should test complete flow to subscribe and unsubscribe and to trigger actions', () => {
let dispatchCount = 0;
const state = {
next: (e: any) => {
dispatchCount++;
expect(e.type).toBe('MOCK_ACTION');
}
};
const mock = new MockupObject(state);
mock.init();
(<any>mock.event1).next('TEST');
(<any>mock.event2).next('TEST');
expect(dispatchCount).toBe(2);
mock.ngOnDestroy();
(<any>mock.event1).next('TEST');
(<any>mock.event2).next('TEST');
expect(dispatchCount).toBe(2);
expect(mock.isDestroyCalled).toBeTruthy();
});
});

69
src/PinkParrot/app/core/angular/action.ts

@ -0,0 +1,69 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
const EMPTY_FUNC = () => { };
export function Action() {
return function (target: any, key: string) {
let observable: any;
let instance: any;
let subscriptions: any;
let subscription: any;
function subscribe() {
const store = instance.store;
if (store && observable && observable.subscribe && typeof observable.subscribe === 'function') {
subscription = observable.subscribe((a: any) => { if (a) { store.next(a); } });
subscriptions.push(subscription);
}
};
function unsubscribe() {
if (subscription) {
subscription.unsubscribe();
subscriptions.splice(subscriptions.indexOf(subscribe), 1);
}
};
if (delete target[key]) {
Object.defineProperty(target, key, {
get: function () {
return observable;
},
set: function (v) {
instance = this;
if (!instance.___subscriptions) {
instance.___subscriptions = [];
let destroy = instance.ngOnDestroy ? instance.ngOnDestroy.bind(instance) : EMPTY_FUNC;
instance.ngOnDestroy = () => {
for (let s of subscriptions) {
s.unsubscribe();
}
instance.___subscriptions = null;
destroy();
};
}
subscriptions = instance.___subscriptions;
observable = v;
unsubscribe();
subscribe();
}
});
}
};
}

1
src/PinkParrot/app/core/angular/cloak.directive.js.map

@ -0,0 +1 @@
{"version":3,"file":"cloak.directive.js","sourceRoot":"","sources":["cloak.directive.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;;;;;;;;;;AAEH,IAAY,GAAG,WAAM,eAAe,CAAC,CAAA;AAKrC;IACI,wBAAoB,QAAgB,EAAE,GAAG,EAAC,UAAU;QAAxC,wBAAwB,GAAxB,kBAAwB;QAAhB,aAAQ,GAAR,QAAQ,CAAQ;IAAoB,CAAC;IAElD,iCAAQ,GAAf;QACI,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC5D,CAAC;IARL;QAAC,GAAG,CAAC,SAAS,CAAC;YACX,QAAQ,EAAE,WAAW;SACxB,CAAC;;sBAAA;IAOF,qBAAC;AAAD,CAAC,AAND,IAMC;AANY,sBAAc,iBAM1B,CAAA"}

1
src/PinkParrot/app/core/angular/cloak.directive.spec.js.map

@ -0,0 +1 @@
{"version":3,"file":"cloak.directive.spec.js","sourceRoot":"","sources":["cloak.directive.spec.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;AAEH,gCAA+B,mBAAmB,CAAC,CAAA;AAEnD,QAAQ,CAAC,gBAAgB,EAAE;IACvB,EAAE,CAAC,8CAA8C,EAAE;QAC/C,IAAI,MAAM,GAAG,KAAK,CAAC;QAEnB,IAAM,OAAO,GAAG;YACZ,aAAa,EAAE;gBACX,SAAS,EAAE;oBACP,MAAM,EAAE;wBACJ,MAAM,GAAG,IAAI,CAAC;oBAClB,CAAC;iBACJ;aACJ;SACJ,CAAC;QAED,IAAI,gCAAc,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;QAExC,MAAM,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,CAAC;IAChC,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}

28
src/PinkParrot/app/core/angular/cloak.directive.spec.ts

@ -0,0 +1,28 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { CloakDirective } from './cloak.directive';
describe('CloakDirective', () => {
it('should remove class from element on ngOnInit', () => {
let called = false;
const element = {
nativeElement: {
classList: {
remove: () => {
called = true;
}
}
}
};
new CloakDirective(element).ngOnInit();
expect(called).toBeTruthy();
});
});

19
src/PinkParrot/app/core/angular/cloak.directive.ts

@ -0,0 +1,19 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
@Ng2.Directive({
selector: '.gp-cloak'
})
export class CloakDirective implements Ng2.OnInit {
constructor(private readonly element: Ng2.ElementRef) { }
public ngOnInit() {
this.element.nativeElement.classList.remove('gp-cloak');
}
}

11
src/PinkParrot/app/core/angular/color-picker.component.html

@ -0,0 +1,11 @@
<div class="btn-group" [class.open]="isOpen">
<button type="button" class="btn btn-secondary btn-sm" (click)="toggleOpen()" [style.background]="selectedColor.toString()"></button>
<div class="dropdown-menu dropdown-menu-right">
<div class="color-palette">
<span *ngFor="let color of palette.colors" class="color-palette-box" (click)="selectColor(color)" [class.selected]="selectedColor && color.eq(selectedColor)">
<span class="color-palette-color" [style.background]="color.toString()"></span>
</span>
</div>
</div>
</div>

1
src/PinkParrot/app/core/angular/color-picker.component.js.map

@ -0,0 +1 @@
{"version":3,"file":"color-picker.component.js","sourceRoot":"","sources":["color-picker.component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;;;;;;;;;;AAEH,IAAY,GAAG,WAAM,eAAe,CAAC,CAAA;AAErC,sBAA6B,kBAAkB,CAAC,CAAA;AAChD,8BAA6B,0BAA0B,CAAC,CAAA;AAOxD;IAmBI,8BAAoB,QAAgB,EAAE,GAAG,EAAC,UAAU;QAAxC,wBAAwB,GAAxB,kBAAwB;QAAhB,aAAQ,GAAR,QAAQ,CAAQ;QAlB5B,uBAAkB,GAAG,IAAI,aAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAGzC,gBAAW,GAAG,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC;QAMrC,YAAO,GAAG,4BAAY,CAAC,MAAM,EAAE,CAAC;QAGhC,WAAM,GAAG,KAAK,CAAC;QAOlB,IAAI,CAAC,WAAW,EAAE,CAAC;IACvB,CAAC;IAND,sBAAW,+CAAa;aAAxB;YACI,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC;QACnC,CAAC;;;OAAA;IAOM,sCAAO,GAAd,UAAe,aAAkB;QAC7B,IAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QAEzE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;YACjB,IAAI,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;IACL,CAAC;IAEM,0CAAW,GAAlB,UAAmB,OAA0B;QACzC,IAAI,CAAC,WAAW,EAAE,CAAC;IACvB,CAAC;IAEM,yCAAU,GAAjB;QACI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC;IAC/B,CAAC;IAEM,oCAAK,GAAZ;QACI,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACxB,CAAC;IAEM,mCAAI,GAAX;QACI,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACvB,CAAC;IAEM,0CAAW,GAAlB,UAAmB,KAAY;QAC3B,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;IAEO,2CAAY,GAApB,UAAqB,KAAY;QAC7B,EAAE,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACpC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;IACL,CAAC;IAEO,0CAAW,GAAnB,UAAoB,KAAa;QAC7B,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC;YACD,IAAI,CAAC,kBAAkB,GAAG,aAAK,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;YAE/D,QAAQ,GAAG,IAAI,CAAC;QACpB,CAAE;QAAA,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACT,QAAQ,GAAG,KAAK,CAAC;QACrB,CAAC;QAED,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;YACxC,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;gBACf,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC;YACxD,CAAC;YAAC,IAAI,CAAC,CAAC;gBACJ,IAAI,CAAC,kBAAkB,GAAG,aAAK,CAAC,KAAK,CAAC;YAC1C,CAAC;QACL,CAAC;IACL,CAAC;IA1ED;QAAC,GAAG,CAAC,MAAM,EAAE;;6DAAA;IAGb;QAAC,GAAG,CAAC,KAAK,EAAE;;uDAAA;IAGZ;QAAC,GAAG,CAAC,KAAK,EAAE;;yDAAA;IAGZ;QAAC,GAAG,CAAC,KAAK,EAAE;;wDAAA;IAWZ;QAAC,GAAG,CAAC,YAAY,CAAC,gBAAgB,EAAE,CAAC,eAAe,CAAC,CAAC;;;;uDAAA;IA5B1D;QAAC,GAAG,CAAC,SAAS,CAAC;YACX,QAAQ,EAAE,iBAAiB;YAC3B,QAAA,MAAM;YACN,UAAA,QAAQ;SACX,CAAC;;4BAAA;IA+EF,2BAAC;AAAD,CAAC,AA9ED,IA8EC;AA9EY,4BAAoB,uBA8EhC,CAAA"}

56
src/PinkParrot/app/core/angular/color-picker.component.scss

@ -0,0 +1,56 @@
@import '../../theme/_mixins.scss';
$color-size: 24px;
$button-size: 1.81rem;
.color-palette {
& {
@include clearfix();
width: 8 * ($color-size + 6);
padding: 6px;
}
&-box {
margin: 2px;
border: 2px solid transparent;
width: $color-size;
height: $color-size;
float: left;
}
&-box:hover {
border-color: #1460A8;
}
&-box.selected {
border-color: #1875CC;
}
&-box.selected:hover {
border-color: #1875CC;
}
&-box.disabled {
border-color: transparent;
}
&-color {
border: 1px solid white;
width: $color-size - 4;
height: $color-size - 4;
display: block;
}
}
.dropdown-menu {
background: #eee;
}
.btn-group > .btn:first-child {
@include border-radius(0.25em);
}
.btn {
width: $button-size;
height: $button-size;
}

1
src/PinkParrot/app/core/angular/color-picker.component.spec.js.map

@ -0,0 +1 @@
{"version":3,"file":"color-picker.component.spec.js","sourceRoot":"","sources":["color-picker.component.spec.ts"],"names":[],"mappings":"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"}

124
src/PinkParrot/app/core/angular/color-picker.component.spec.ts

@ -0,0 +1,124 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Color } from './../';
import { ColorPickerComponent } from './color-picker.component';
describe('ColorPickerComponent', () => {
it('should instantiate', () => {
const colorPicker = new ColorPickerComponent({ nativeElement: {} });
expect(colorPicker).toBeDefined();
});
it('should close color picker when clicking outside of the modal', () => {
const element = {
nativeElement: {
contains: () => {
return false;
}
}
};
const colorPicker = new ColorPickerComponent(element);
colorPicker.open();
colorPicker.onClick({});
expect(colorPicker.isOpen).toBeFalsy();
});
it('should not close color picker when clicking inside the modal', () => {
const element = {
nativeElement: {
contains: () => {
return true;
}
}
};
const colorPicker = new ColorPickerComponent(element);
colorPicker.open();
colorPicker.onClick({});
expect(colorPicker.isOpen).toBeTruthy();
});
it('should close modal and emit event when setting color', () => {
const colorPicker = new ColorPickerComponent({ nativeElement: {} });
const selectedColor = Color.RED;
let lastColor: Color | null = null;
colorPicker.colorChange.subscribe((c: Color) => {
lastColor = c;
});
colorPicker.open();
colorPicker.selectColor(selectedColor);
expect(lastColor).toBe(selectedColor);
expect(colorPicker.isOpen).toBeFalsy();
});
it('should not emit event when selecting same color', () => {
const colorPicker = new ColorPickerComponent({ nativeElement: {} });
const selectedColor = Color.RED;
colorPicker.selectColor(selectedColor);
let lastColor: Color | null = null;
colorPicker.colorChange.subscribe((c: Color) => {
lastColor = c;
});
colorPicker.selectColor(selectedColor);
expect(lastColor).toBeNull();
});
it('should update selected color when component changes', () => {
const colorPicker = new ColorPickerComponent({ nativeElement: {} });
const selectedColor = Color.RED;
colorPicker.color = selectedColor;
colorPicker.ngOnChanges({});
expect(colorPicker.selectedColor).toBe(selectedColor);
});
it('should update selected color with palette default if setting invalid color', () => {
const colorPicker = new ColorPickerComponent({ nativeElement: {} });
colorPicker.color = 'invalid';
colorPicker.ngOnChanges({});
expect(colorPicker.selectedColor).toBe(colorPicker.palette.defaultColor);
});
it('should update selected color with black if setting invalid color and palette is null', () => {
const colorPicker = new ColorPickerComponent({ nativeElement: {} });
colorPicker.palette = undefined!;
colorPicker.color = 'invalid';
colorPicker.ngOnChanges({});
expect(colorPicker.selectedColor).toBe(Color.BLACK);
});
it('should update isOpen prperty when toggleOpen is invoked', () => {
const colorPicker = new ColorPickerComponent({ nativeElement: {} });
colorPicker.toggleOpen();
expect(colorPicker.isOpen).toBeTruthy();
});
});

96
src/PinkParrot/app/core/angular/color-picker.component.ts

@ -0,0 +1,96 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { Color } from './../utils/color';
import { ColorPalette } from './../utils/color-palette';
@Ng2.Component({
selector: 'gp-color-picker',
styles,
template
})
export class ColorPickerComponent implements Ng2.OnChanges {
private selectedColorValue = new Color(0, 0, 0);
@Ng2.Output()
public colorChange = new Ng2.EventEmitter();
@Ng2.Input()
public color: string | number | Color;
@Ng2.Input()
public palette = ColorPalette.colors();
@Ng2.Input()
public isOpen = false;
public get selectedColor(): Color {
return this.selectedColorValue;
}
constructor(private readonly element: Ng2.ElementRef) {
this.updateColor();
}
@Ng2.HostListener('document:click', ['$event.target'])
public onClick(targetElement: any) {
const clickedInside = this.element.nativeElement.contains(targetElement);
if (!clickedInside) {
this.close();
}
}
public ngOnChanges(changes: Ng2.SimpleChanges) {
this.updateColor();
}
public toggleOpen() {
this.isOpen = !this.isOpen;
}
public close() {
this.isOpen = false;
}
public open() {
this.isOpen = true;
}
public selectColor(color: Color) {
this.updateParent(color);
this.updateColor(color);
this.close();
}
private updateParent(color: Color) {
if (this.selectedColorValue.ne(color)) {
this.colorChange.emit(color);
}
}
private updateColor(color?: Color) {
let hasColor = false;
try {
this.selectedColorValue = Color.fromValue(color || this.color);
hasColor = true;
} catch (e) {
hasColor = false;
}
if (!hasColor || !this.selectedColorValue) {
if (this.palette) {
this.selectedColorValue = this.palette.defaultColor;
} else {
this.selectedColorValue = Color.BLACK;
}
}
}
}

1
src/PinkParrot/app/core/angular/date-time.pipes.js.map

@ -0,0 +1 @@
{"version":3,"file":"date-time.pipes.js","sourceRoot":"","sources":["date-time.pipes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;;;;;;;;;;AAEH,IAAY,GAAG,WAAM,eAAe,CAAC,CAAA;AAQrC;IAAA;IAIA,CAAC;IAHU,iCAAS,GAAhB,UAAiB,KAAe,EAAE,IAAc;QAC5C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC1C,CAAC;IANL;QAAC,GAAG,CAAC,IAAI,CAAC;YACN,IAAI,EAAE,WAAW;SACpB,CAAC;;qBAAA;IAKF,oBAAC;AAAD,CAAC,AAJD,IAIC;AAJY,qBAAa,gBAIzB,CAAA;AAKD;IAAA;IAIA,CAAC;IAHU,6BAAS,GAAhB,UAAiB,KAAe,EAAE,IAAc;QAC5C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC;IANL;QAAC,GAAG,CAAC,IAAI,CAAC;YACN,IAAI,EAAE,OAAO;SAChB,CAAC;;iBAAA;IAKF,gBAAC;AAAD,CAAC,AAJD,IAIC;AAJY,iBAAS,YAIrB,CAAA;AAKD;IAAA;IAIA,CAAC;IAHU,iCAAS,GAAhB,UAAiB,KAAe,EAAE,IAAc;QAC5C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IANL;QAAC,GAAG,CAAC,IAAI,CAAC;YACN,IAAI,EAAE,WAAW;SACpB,CAAC;;qBAAA;IAKF,oBAAC;AAAD,CAAC,AAJD,IAIC;AAJY,qBAAa,gBAIzB,CAAA;AAKD;IAAA;IAIA,CAAC;IAHU,2BAAS,GAAhB,UAAiB,KAAe,EAAE,IAAc;QAC5C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IANL;QAAC,GAAG,CAAC,IAAI,CAAC;YACN,IAAI,EAAE,KAAK;SACd,CAAC;;eAAA;IAKF,cAAC;AAAD,CAAC,AAJD,IAIC;AAJY,eAAO,UAInB,CAAA;AAKD;IAAA;IAIA,CAAC;IAHU,iCAAS,GAAhB,UAAiB,KAAe,EAAE,IAAc;QAC5C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IANL;QAAC,GAAG,CAAC,IAAI,CAAC;YACN,IAAI,EAAE,WAAW;SACpB,CAAC;;qBAAA;IAKF,oBAAC;AAAD,CAAC,AAJD,IAIC;AAJY,qBAAa,gBAIzB,CAAA;AAKD;IAAA;IAIA,CAAC;IAHU,gCAAS,GAAhB,UAAiB,KAAe,EAAE,IAAc;QAC5C,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC5B,CAAC;IANL;QAAC,GAAG,CAAC,IAAI,CAAC;YACN,IAAI,EAAE,UAAU;SACnB,CAAC;;oBAAA;IAKF,mBAAC;AAAD,CAAC,AAJD,IAIC;AAJY,oBAAY,eAIxB,CAAA"}

1
src/PinkParrot/app/core/angular/date-time.pipes.spec.js.map

@ -0,0 +1 @@
{"version":3,"file":"date-time.pipes.spec.js","sourceRoot":"","sources":["date-time.pipes.spec.ts"],"names":[],"mappings":"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"}

87
src/PinkParrot/app/core/angular/date-time.pipes.spec.ts

@ -0,0 +1,87 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { DateTime, Duration } from './../';
import {
DayOfWeekPipe,
DayPipe,
DurationPipe,
MonthPipe,
ShortDatePipe,
ShortTimePipe,
} from './date-time.pipes';
const dateTime = DateTime.parse('2013-10-03T12:13:14.125', DateTime.iso8601());
describe('DurationPipe', () => {
it('should format to standard duration string', () => {
const duration = Duration.create(dateTime, dateTime.addMinutes(10).addDays(13));
const pipe = new DurationPipe();
const actual = pipe.transform(duration, []);
const expected = '312:10h';
expect(actual).toBe(expected);
});
});
describe('DayPipe', () => {
it('should format to day numbers', () => {
const pipe = new DayPipe();
const actual = pipe.transform(dateTime, []);
const expected = '03';
expect(actual).toBe(expected);
});
});
describe('MonthPipe', () => {
it('should format to long month name', () => {
const pipe = new MonthPipe();
const actual = pipe.transform(dateTime, []);
const expected = 'October';
expect(actual).toBe(expected);
});
});
describe('DayOfWeekPipe', () => {
it('should format to short week of day string', () => {
const pipe = new DayOfWeekPipe();
const actual = pipe.transform(dateTime, []);
const expected = 'Th';
expect(actual).toBe(expected);
});
});
describe('ShortDatePipe', () => {
it('should format to two digit day number and short month name', () => {
const pipe = new ShortDatePipe();
const actual = pipe.transform(dateTime, []);
const expected = '03.Oct';
expect(actual).toBe(expected);
});
});
describe('ShortTimePipe', () => {
it('should format to short time string', () => {
const pipe = new ShortTimePipe();
const actual = pipe.transform(dateTime, []);
const expected = '12:13';
expect(actual).toBe(expected);
});
});

65
src/PinkParrot/app/core/angular/date-time.pipes.ts

@ -0,0 +1,65 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { DateTime } from './../utils/date-time';
import { Duration } from './../utils/duration';
@Ng2.Pipe({
name: 'shortDate'
})
export class ShortDatePipe {
public transform(value: DateTime, args: string[]): any {
return value.toStringFormat('DD.MMM');
}
}
@Ng2.Pipe({
name: 'month'
})
export class MonthPipe {
public transform(value: DateTime, args: string[]): any {
return value.toStringFormat('MMMM');
}
}
@Ng2.Pipe({
name: 'dayOfWeek'
})
export class DayOfWeekPipe {
public transform(value: DateTime, args: string[]): any {
return value.toStringFormat('dd');
}
}
@Ng2.Pipe({
name: 'day'
})
export class DayPipe {
public transform(value: DateTime, args: string[]): any {
return value.toStringFormat('DD');
}
}
@Ng2.Pipe({
name: 'shortTime'
})
export class ShortTimePipe {
public transform(value: DateTime, args: string[]): any {
return value.toStringFormat('HH:mm');
}
}
@Ng2.Pipe({
name: 'duration'
})
export class DurationPipe {
public transform(value: Duration, args: string[]): any {
return value.toString();
}
}

1
src/PinkParrot/app/core/angular/drag-model.directive.js.map

@ -0,0 +1 @@
{"version":3,"file":"drag-model.directive.js","sourceRoot":"","sources":["drag-model.directive.ts"],"names":[],"mappings":"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"}

117
src/PinkParrot/app/core/angular/drag-model.directive.ts

@ -0,0 +1,117 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { DragService, Vec2 } from './../';
@Ng2.Directive({
selector: '[gpDragModel]'
})
export class DragModelDirective {
private startOffset: Vec2;
private startPosition: Vec2;
private mouseMoveSubscription: Function | null;
private mouseUpSubscription: Function | null;
private clonedElement: HTMLElement | null;
@Ng2.Input('gpDragModel')
public model: any;
constructor(
private readonly element: Ng2.ElementRef,
private readonly renderer: Ng2.Renderer,
private readonly dragService: DragService
) {
}
@Ng2.HostListener('mousedown', ['$event'])
public onMouseDown(event: MouseEvent) {
this.startOffset = new Vec2(event.offsetX, event.offsetY);
this.startPosition = new Vec2(event.clientX, event.clientY);
this.mouseMoveSubscription =
this.renderer.listenGlobal('window', 'mousemove', (e: MouseEvent) => {
this.onMouseMove(e);
});
this.mouseUpSubscription =
this.renderer.listenGlobal('window', 'mouseup', (e: MouseEvent) => {
this.onMouseUp(e);
});
this.stopEvent(event);
}
private onMouseMove(event: MouseEvent) {
const position = new Vec2(event.clientX, event.clientY);
if (!this.clonedElement && position.sub(this.startPosition).lengtSquared > 100) {
this.clonedElement = this.element.nativeElement.cloneNode(true);
this.clonedElement!.style.position = 'fixed';
this.clonedElement!.style.zIndex = '10000';
document.body.appendChild(this.clonedElement!);
}
if (this.clonedElement) {
const elementPosition = position.sub(this.startOffset);
this.clonedElement.style.left = elementPosition.x + 'px';
this.clonedElement.style.top = elementPosition.y + 'px';
}
this.stopEvent(event);
}
private onMouseUp(event: MouseEvent) {
if (this.clonedElement) {
this.clonedElement.remove();
}
let dropCandidate: Element | null = document.elementFromPoint(event.clientX, event.clientY);
while (dropCandidate && (dropCandidate.classList && !dropCandidate.classList.contains('gp-drop'))) {
dropCandidate = dropCandidate.parentNode as Element;
}
if (dropCandidate && dropCandidate.id) {
const position = this.getRelativeCoordinates(event, dropCandidate).sub(this.startOffset).round();
this.dragService.emitDrop({ position, model: this.model, dropTarget: dropCandidate.id });
}
if (this.mouseMoveSubscription) {
this.mouseMoveSubscription();
this.mouseMoveSubscription = null;
}
if (this.mouseUpSubscription) {
this.mouseUpSubscription();
this.mouseUpSubscription = null;
}
this.clonedElement = null;
this.stopEvent(event);
}
private stopEvent(event: Event) {
event.preventDefault();
event.stopPropagation();
}
private getRelativeCoordinates(e: any, container: any): Vec2 {
const rect = container.getBoundingClientRect();
const x = !!e.touches ? e.touches[0].pageX : e.pageX;
const y = !!e.touches ? e.touches[0].pageY : e.pageY;
return new Vec2(x - rect.left, y - rect.top);
}
}

1
src/PinkParrot/app/core/angular/focus-on-change.directive.js.map

@ -0,0 +1 @@
{"version":3,"file":"focus-on-change.directive.js","sourceRoot":"","sources":["focus-on-change.directive.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;;;;;;;;;;AAEH,IAAY,GAAG,WAAM,eAAe,CAAC,CAAA;AAKrC;IAII,gCACY,QAAmB,EAAE,GAAG,EAAC,UAAU,EACnC,QAAiB,EAAE,GAAG,EAAC,QAAQ;QADvC,wBAA2B,GAA3B,qBAA2B;QAC3B,wBAAyB,GAAzB,mBAAyB;QADjB,aAAQ,GAAR,QAAQ,CAAW;QACnB,aAAQ,GAAR,QAAQ,CAAS;IAE7B,CAAC;IAEM,4CAAW,GAAlB,UAAmB,OAA4C;QAA/D,iBAIC;QAHG,UAAU,CAAC;YACP,KAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,KAAI,CAAC,UAAU,CAAC,aAAa,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;QAClF,CAAC,EAAE,GAAG,CAAC,CAAC;IACZ,CAAC;IAbD;QAAC,GAAG,CAAC,KAAK,EAAE;;mEAAA;IAJhB;QAAC,GAAG,CAAC,SAAS,CAAC;YACX,QAAQ,EAAE,mBAAmB;SAChC,CAAC;;8BAAA;IAgBF,6BAAC;AAAD,CAAC,AAfD,IAeC;AAfY,8BAAsB,yBAelC,CAAA"}

1
src/PinkParrot/app/core/angular/focus-on-change.directive.spec.js.map

@ -0,0 +1 @@
{"version":3,"file":"focus-on-change.directive.spec.js","sourceRoot":"","sources":["focus-on-change.directive.spec.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;AAIH,0CAAuC,6BAA6B,CAAC,CAAA;AAErE,QAAQ,CAAC,wBAAwB,EAAE;IAC/B,IAAI,eAAe,GAAG,CAAC,CAAC;IAExB,UAAU,CAAC;QACP,eAAe,GAAG,OAAO,CAAC,wBAAwB,CAAC;QAEnD,OAAO,CAAC,wBAAwB,GAAG,GAAG,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,UAAC,IAAS;QAC5D,IAAI,YAAiB,CAAC;QACtB,IAAI,aAAkB,CAAC;QAEvB,IAAM,QAAQ,GAAG;YACb,mBAAmB,EAAE,UAAC,OAAY,EAAE,MAAW,EAAE,IAAS;gBACtD,aAAa,GAAG,OAAO,CAAC;gBACxB,YAAY,GAAG,MAAM,CAAC;YAC1B,CAAC;SACJ,CAAC;QAEF,IAAM,OAAO,GAAmB;YAC5B,aAAa,EAAE,EAAE;SACpB,CAAC;QAEF,IAAI,kDAAsB,CAAC,OAAO,EAAE,QAAwB,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAE9E,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACvC,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QAExC,UAAU,CAAC;YACP,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACnC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;YAElD,IAAI,EAAE,CAAC;QACX,CAAC,EAAE,GAAG,CAAC,CAAC;IACZ,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC;QACN,OAAO,CAAC,wBAAwB,GAAG,eAAe,CAAC;IACvD,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}

52
src/PinkParrot/app/core/angular/focus-on-change.directive.spec.ts

@ -0,0 +1,52 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { FocusOnChangeDirective } from './focus-on-change.directive';
describe('FocusOnChangeDirective', () => {
let originalTimeout = 0;
beforeEach(() => {
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 800;
});
it('should call focus on element when value changes', (done: any) => {
let calledMethod: any;
let calledElement: any;
const renderer = {
invokeElementMethod: (element: any, method: any, args: any) => {
calledElement = element;
calledMethod = method;
}
};
const element: Ng2.ElementRef = {
nativeElement: {}
};
new FocusOnChangeDirective(element, renderer as Ng2.Renderer).ngOnChanges({});
expect(calledMethod).not.toBeDefined();
expect(calledElement).not.toBeDefined();
setTimeout(() => {
expect(calledMethod).toBe('focus');
expect(calledElement).toBe(element.nativeElement);
done();
}, 400);
});
afterEach(() => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
});

28
src/PinkParrot/app/core/angular/focus-on-change.directive.ts

@ -0,0 +1,28 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
@Ng2.Directive({
selector: '[gpFocusOnChange]'
})
export class FocusOnChangeDirective implements Ng2.OnChanges {
@Ng2.Input()
public gpFocusOnChange: any;
constructor(
private readonly elementRef: Ng2.ElementRef,
private readonly renderer: Ng2.Renderer
) {
}
public ngOnChanges(changes: { [key: string]: Ng2.SimpleChange }) {
setTimeout(() => {
this.renderer.invokeElementMethod(this.elementRef.nativeElement, 'focus', []);
}, 100);
}
}

1
src/PinkParrot/app/core/angular/image-drop.directive.js.map

@ -0,0 +1 @@
{"version":3,"file":"image-drop.directive.js","sourceRoot":"","sources":["image-drop.directive.ts"],"names":[],"mappings":"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"}

127
src/PinkParrot/app/core/angular/image-drop.directive.ts

@ -0,0 +1,127 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { DragService, Vec2 } from './../';
@Ng2.Directive({
selector: '.gp-image-drop'
})
export class ImageDropDirective {
constructor(
private readonly element: Ng2.ElementRef,
private readonly renderer: Ng2.Renderer,
private readonly dragService: DragService
) {
}
@Ng2.HostListener('dragenter', ['$event'])
public onDragEnter(event: DragDropEvent) {
this.tryStopEvent(event);
}
@Ng2.HostListener('dragover', ['$event'])
public onDragOver(event: DragDropEvent) {
this.tryStopEvent(event);
}
@Ng2.HostListener('drop', ['$event'])
public onDrop(event: DragDropEvent) {
const image = this.findImage(event);
if (!image) {
return;
}
const position = this.getRelativeCoordinates(event, this.element.nativeElement).round();
const reader = new FileReader();
reader.onload = (loadedFile: any) => {
const imageSource: string = loadedFile.target.result;
const imageElement = document.createElement('img');
imageElement.onload = () => {
this.dragService.emitDrop({
position, dropTarget: this.element.nativeElement.id, model: {
sizeX: imageElement.width,
sizeY: imageElement.height,
source: imageSource
}
});
};
imageElement.src = imageSource;
};
reader.readAsDataURL(image);
this.stopEvent(event);
}
private stopEvent(event: Event) {
event.preventDefault();
event.stopPropagation();
}
private tryStopEvent(event: DragDropEvent) {
const hasFiles = this.hasFiles(event.dataTransfer.types);
if (!hasFiles) {
return;
}
this.stopEvent(event);
}
private hasFiles(types: any): boolean {
if (!types) {
return false;
}
if (isFunction(types.indexOf)) {
return types.indexOf('Files') !== -1;
} else if (isFunction(types.contains)) {
return types.contains('Files');
} else {
return false;
}
}
private findImage(event: DragDropEvent): File | null {
let image: File | null = null;
for (let i = 0; i < event.dataTransfer.files.length; i++) {
const file = event.dataTransfer.files[i];
if (file.type.match('image.*')) {
image = file;
break;
}
}
return image;
}
private getRelativeCoordinates(e: any, container: any): Vec2 {
const rect = container.getBoundingClientRect();
let pos = { x: 0, y: 0 };
pos.x = !!e.touches ? e.touches[0].pageX : e.pageX;
pos.y = !!e.touches ? e.touches[0].pageY : e.pageY;
return new Vec2(pos.x - rect.left, pos.y - rect.top);
}
}
function isFunction(obj: any): boolean {
return !!(obj && obj.constructor && obj.call && obj.apply);
};
interface DragDropEvent extends MouseEvent {
readonly dataTransfer: DataTransfer;
}

1
src/PinkParrot/app/core/angular/money.pipe.js.map

@ -0,0 +1 @@
{"version":3,"file":"money.pipe.js","sourceRoot":"","sources":["money.pipe.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;;;;;;;;;;AAEH,IAAY,GAAG,WAAM,eAAe,CAAC,CAAA;AAOrC;IACI,mBACY,QAAiB,EAAE,cAAc,EACjC,QAAkB,EAAE,sBAAsB;QADlD,wBAAyB,GAAzB,mBAAyB;QACzB,wBAA0B,GAA1B,oBAA0B;QADlB,aAAQ,GAAR,QAAQ,CAAS;QACjB,aAAQ,GAAR,QAAQ,CAAU;IAE9B,CAAC;IAEM,6BAAS,GAAhB,UAAiB,KAAa,EAAE,IAAc;QAC1C,IAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAE1C,IAAI,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,GAAG,wBAAwB,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC;QAEjJ,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;YAC1B,MAAM,GAAG,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QACjD,CAAC;QAAC,IAAI,CAAC,CAAC;YACJ,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC;QACjD,CAAC;QAED,MAAM,CAAC,MAAM,CAAC;IAClB,CAAC;IAtBL;QAAC,GAAG,CAAC,IAAI,CAAC;YACN,IAAI,EAAE,OAAO;SAChB,CAAC;;iBAAA;IAqBF,gBAAC;AAAD,CAAC,AApBD,IAoBC;AApBY,iBAAS,YAoBrB,CAAA"}

1
src/PinkParrot/app/core/angular/money.pipe.spec.js.map

@ -0,0 +1 @@
{"version":3,"file":"money.pipe.spec.js","sourceRoot":"","sources":["money.pipe.spec.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;AAEH,iBAAuD,OAAO,CAAC,CAAA;AAE/D,2BAA0B,cAAc,CAAC,CAAA;AAEzC,QAAQ,CAAC,WAAW,EAAE;IAClB,EAAE,CAAC,qDAAqD,EAAE;QACtD,IAAM,IAAI,GAAG,IAAI,sBAAS,CAAC,IAAI,iBAAc,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,IAAI,yBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC;QAE5F,IAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC1C,IAAM,QAAQ,GAAG,uCAAuC,CAAC;QAEzD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE;QACtE,IAAM,IAAI,GAAG,IAAI,sBAAS,CAAC,IAAI,iBAAc,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,IAAI,yBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC;QAE5F,IAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACzC,IAAM,QAAQ,GAAG,uCAAuC,CAAC;QAEzD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE;QACvD,IAAM,IAAI,GAAG,IAAI,sBAAS,CAAC,IAAI,iBAAc,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,IAAI,yBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC;QAEnG,IAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC1C,IAAM,QAAQ,GAAG,uCAAuC,CAAC;QAEzD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sEAAsE,EAAE;QACvE,IAAM,IAAI,GAAG,IAAI,sBAAS,CAAC,IAAI,iBAAc,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,IAAI,yBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC;QAEnG,IAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACzC,IAAM,QAAQ,GAAG,uCAAuC,CAAC;QAEzD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}

48
src/PinkParrot/app/core/angular/money.pipe.spec.ts

@ -0,0 +1,48 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { CurrencyConfig, DecimalSeparatorConfig } from './../';
import { MoneyPipe } from './money.pipe';
describe('MoneyPipe', () => {
it('should format money values with symbol after number', () => {
const pipe = new MoneyPipe(new CurrencyConfig('EUR', '€'), new DecimalSeparatorConfig(','));
const actual = pipe.transform(123.49, []);
const expected = '123,<span class="decimal">49</span> €';
expect(actual).toBe(expected);
});
it('should format money values with symbol after number and one decimal', () => {
const pipe = new MoneyPipe(new CurrencyConfig('EUR', '€'), new DecimalSeparatorConfig(','));
const actual = pipe.transform(123.4, []);
const expected = '123,<span class="decimal">40</span> €';
expect(actual).toBe(expected);
});
it('should format money values with symbol before number', () => {
const pipe = new MoneyPipe(new CurrencyConfig('EUR', '€', false), new DecimalSeparatorConfig(','));
const actual = pipe.transform(123.49, []);
const expected = '€ 123,<span class="decimal">49</span>';
expect(actual).toBe(expected);
});
it('should format money values with symbol before number and one decimal', () => {
const pipe = new MoneyPipe(new CurrencyConfig('EUR', '€', false), new DecimalSeparatorConfig(','));
const actual = pipe.transform(123.4, []);
const expected = '€ 123,<span class="decimal">40</span>';
expect(actual).toBe(expected);
});
});

37
src/PinkParrot/app/core/angular/money.pipe.ts

@ -0,0 +1,37 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { CurrencyConfig, DecimalSeparatorConfig } from './../configurations';
@Ng2.Pipe({
name: 'money'
})
export class MoneyPipe {
constructor(
private readonly currency: CurrencyConfig,
private readonly separator: DecimalSeparatorConfig
) {
}
public transform(value: number, args: string[]): any {
const money = value.toFixed(2).toString();
let result = money.substr(0, money.length - 3) + this.separator.value + '<span class="decimal">' + money.substr(money.length - 2, 2) + '</span>';
if (this.currency.showAfter) {
result = result + ' ' + this.currency.symbol;
} else {
result = this.currency.symbol + ' ' + result;
}
return result;
}
}

1
src/PinkParrot/app/core/angular/shortcut.component.js.map

@ -0,0 +1 @@
{"version":3,"file":"shortcut.component.js","sourceRoot":"","sources":["shortcut.component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;;;;;;;;;;AAEH,IAAY,GAAG,WAAM,eAAe,CAAC,CAAA;AAQrC;IAYI,2BACY,QAAwB,EAAE,eAAe,EACzC,QAAa,EAAE,GAAG,EAAC,MAAM;QADjC,wBAAgC,GAAhC,0BAAgC;QAChC,wBAAqB,GAArB,eAAqB;QADb,aAAQ,GAAR,QAAQ,CAAgB;QACxB,aAAQ,GAAR,QAAQ,CAAK;QANlB,YAAO,GAAG,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC;IAQxC,CAAC;IAEM,oCAAQ,GAAf;QAAA,iBAcC;QAbG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;QAE1B,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YAChB,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAA,CAAC;gBACpC,EAAE,CAAC,CAAC,CAAC,KAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;oBACjB,KAAI,CAAC,IAAI,CAAC,GAAG,CAAC;wBACV,KAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;oBACzB,CAAC,CAAC,CAAC;gBACP,CAAC;gBAED,MAAM,CAAC,KAAK,CAAC;YACjB,CAAC,CAAC,CAAC;QACP,CAAC;IACL,CAAC;IACM,uCAAW,GAAlB;QACI,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YAChB,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC;IACL,CAAC;IApCD;QAAC,GAAG,CAAC,KAAK,EAAE;;mDAAA;IAGZ;QAAC,GAAG,CAAC,KAAK,EAAE;;uDAAA;IAGZ;QAAC,GAAG,CAAC,MAAM,EAAE;;sDAAA;IAXjB;QAAC,GAAG,CAAC,SAAS,CAAC;YACX,QAAQ,EAAE,aAAa;YACvB,QAAQ,EAAE,EAAE;SACf,CAAC;;yBAAA;IAuCF,wBAAC;AAAD,CAAC,AAtCD,IAsCC;AAtCY,yBAAiB,oBAsC7B,CAAA"}

1
src/PinkParrot/app/core/angular/shortcut.component.spec.js.map

@ -0,0 +1 @@
{"version":3,"file":"shortcut.component.spec.js","sourceRoot":"","sources":["shortcut.component.spec.ts"],"names":[],"mappings":"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"}

83
src/PinkParrot/app/core/angular/shortcut.component.spec.ts

@ -0,0 +1,83 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { ShortcutService } from './../';
import { ShortcutComponent } from './shortcut.component';
describe('ShortcutComponent', () => {
let shortcutService: ShortcutService;
beforeEach(() => {
shortcutService = new ShortcutService();
});
it('should instantiate', () => {
const shortcutComponent = new ShortcutComponent(shortcutService, new Ng2.NgZone({}));
expect(shortcutComponent).toBeDefined();
});
it('should init without keys', () => {
const shortcutComponent = new ShortcutComponent(shortcutService, new Ng2.NgZone({}));
shortcutComponent.keys = null!;
shortcutComponent.ngOnInit();
});
it('should destroy without keys', () => {
const shortcutComponent = new ShortcutComponent(shortcutService, new Ng2.NgZone({}));
shortcutComponent.keys = null!;
shortcutComponent.ngOnDestroy();
});
it('should raise event when triggered', () => {
const shortcutComponent = new ShortcutComponent(shortcutService, new Ng2.NgZone({}));
let isTriggered = false;
shortcutComponent.keys = 'ctrl+a';
shortcutComponent.ngOnInit();
shortcutComponent.trigger.subscribe(() => { isTriggered = true; });
shortcutService.trigger('ctrl+a');
expect(isTriggered).toBeTruthy();
});
it('should not raise event when triggered but disabled', () => {
const shortcutComponent = new ShortcutComponent(shortcutService, new Ng2.NgZone({}));
let isTriggered = false;
shortcutComponent.keys = 'ctrl+a';
shortcutComponent.ngOnInit();
shortcutComponent.trigger.subscribe(() => { isTriggered = true; });
shortcutComponent.disabled = true;
shortcutService.trigger('ctrl+a');
expect(isTriggered).toBeFalsy();
});
it('should not raise event when triggered but destroyed', () => {
const shortcutComponent = new ShortcutComponent(shortcutService, new Ng2.NgZone({}));
let isTriggered = false;
shortcutComponent.keys = 'ctrl+a';
shortcutComponent.ngOnInit();
shortcutComponent.trigger.subscribe(() => { isTriggered = true; });
shortcutComponent.ngOnDestroy();
shortcutService.trigger('ctrl+a');
expect(isTriggered).toBeFalsy();
});
});

54
src/PinkParrot/app/core/angular/shortcut.component.ts

@ -0,0 +1,54 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { ShortcutService } from './../services/shortcut.service';
@Ng2.Component({
selector: 'gp-shortcut',
template: ''
})
export class ShortcutComponent implements Ng2.OnInit, Ng2.OnDestroy {
@Ng2.Input()
public keys: string;
@Ng2.Input()
public disabled: boolean;
@Ng2.Output()
public trigger = new Ng2.EventEmitter();
private lastKeys: string;
constructor(
private readonly shortcutService: ShortcutService,
private readonly zone: Ng2.NgZone
) {
}
public ngOnInit() {
this.lastKeys = this.keys;
if (this.lastKeys) {
this.shortcutService.on(this.lastKeys, e => {
if (!this.disabled) {
this.zone.run(() => {
this.trigger.next(e);
});
}
return false;
});
}
}
public ngOnDestroy() {
if (this.lastKeys) {
this.shortcutService.off(this.lastKeys);
}
}
}

3
src/PinkParrot/app/core/angular/slider.component.html

@ -0,0 +1,3 @@
<div class="slider-bar" #bar (click)="onBarMouseClick($event)">
<div class="slider-thumb" #thumb (mousedown)="onThumbMouseDown($event)"></div>
</div>

1
src/PinkParrot/app/core/angular/slider.component.js.map

@ -0,0 +1 @@
{"version":3,"file":"slider.component.js","sourceRoot":"","sources":["slider.component.ts"],"names":[],"mappings":"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"}

37
src/PinkParrot/app/core/angular/slider.component.scss

@ -0,0 +1,37 @@
@import '../../theme/_mixins.scss';
$bar-height: 12px;
$thumb-size: 20px;
$thumb-margin: ($thumb-size - $bar-height) * 0.5;
$color-border: #ccc;
$color-focus: #66afe9;
.slider {
&-bar {
@include border-radius($bar-height * 0.5);
position: relative;
border: 1px solid $color-border;
margin-bottom: 20px;
margin-top: 5px;
margin-right: $thumb-size * 0.5;
background: white;
height: $bar-height;
}
&-thumb {
@include border-radius($thumb-size * 0.5);
position: absolute;
width: $thumb-size;
height: $thumb-size;
border: 1px solid $color-border;
background: white;
margin-top: -$thumb-margin;
margin-left: -$thumb-size * 0.5;
}
&-thumb.focused {
border-color: $color-focus;
}
}

145
src/PinkParrot/app/core/angular/slider.component.ts

@ -0,0 +1,145 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
@Ng2.Component({
selector: 'gp-slider',
styles,
template
})
export class SliderComponent implements Ng2.OnChanges {
private mouseMoveSubscription: Function | null;
private mouseUpSubscription: Function | null;
private centerStartOffset = 0;
private startValue: number;
@Ng2.ViewChild('bar')
public bar: Ng2.ElementRef;
@Ng2.ViewChild('thumb')
public thumb: Ng2.ElementRef;
@Ng2.Input()
public min: number = 0;
@Ng2.Input()
public max: number = 100;
@Ng2.Input()
public value: number;
@Ng2.Output()
public valueChange = new Ng2.EventEmitter();
constructor(private readonly renderer: Ng2.Renderer) { }
public ngOnChanges(changes: Ng2.SimpleChanges) {
const relativeValue = (this.value - this.min) / (this.max - this.min);
this.setThumbPosition(relativeValue);
}
public onBarMouseClick(event: MouseEvent) {
const relativeValue = this.getRelativeX(event);
this.setThumbPosition(relativeValue);
const newValue = Math.round(relativeValue * (this.max - this.min) + this.min);
if (newValue !== this.value) {
this.valueChange.emit(newValue);
}
this.stopEvent(event);
}
public onThumbMouseDown(event: MouseEvent) {
this.centerStartOffset = event.offsetX - this.thumb.nativeElement.clientWidth * 0.5;
this.startValue = this.value;
this.mouseMoveSubscription =
this.renderer.listenGlobal('window', 'mousemove', (e: MouseEvent) => {
this.onMouseMove(e);
});
this.mouseUpSubscription =
this.renderer.listenGlobal('window', 'mouseup', (e: MouseEvent) => {
this.onMouseUp(e);
});
this.renderer.setElementClass(this.thumb.nativeElement, 'focused', true);
this.stopEvent(event);
}
private onMouseMove(event: MouseEvent) {
const relativeValue = this.getRelativeX(event);
this.setThumbPosition(relativeValue);
this.stopEvent(event);
}
private onMouseUp(event: MouseEvent) {
const relativeValue = this.getRelativeX(event);
const newValue = Math.round(relativeValue * (this.max - this.min) + this.min);
if (newValue !== this.startValue) {
this.valueChange.emit(newValue);
}
this.releaseMouseHandlers();
this.renderer.setElementClass(this.thumb.nativeElement, 'focused', false);
this.centerStartOffset = 0;
this.stopEvent(event);
}
private getRelativeX(event: MouseEvent): number {
const parentOffsetX = this.getParentX(event, this.bar.nativeElement) - this.centerStartOffset;
const parentWidth = this.bar.nativeElement.clientWidth;
const relativeValue = Math.min(1, Math.max(0, (parentOffsetX - this.centerStartOffset) / parentWidth));
return relativeValue;
}
private getParentX(e: any, container: any): number {
const rect = container.getBoundingClientRect();
const x = !!e.touches ? e.touches[0].pageX : e.pageX;
return x - rect.left;
}
private setThumbPosition(relativeValue: number) {
relativeValue = Math.min(1, Math.max(0, relativeValue));
this.renderer.setElementStyle(this.thumb.nativeElement, 'left', relativeValue * 100 + '%');
}
private stopEvent(event: Event) {
event.preventDefault();
event.stopPropagation();
}
private releaseMouseHandlers() {
if (this.mouseMoveSubscription) {
this.mouseMoveSubscription();
this.mouseMoveSubscription = null;
}
if (this.mouseUpSubscription) {
this.mouseUpSubscription();
this.mouseUpSubscription = null;
}
}
}

1
src/PinkParrot/app/core/angular/spinner.component.js.map

@ -0,0 +1 @@
{"version":3,"file":"spinner.component.js","sourceRoot":"","sources":["spinner.component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;;;;;;;;;;AAEH,IAAY,GAAG,WAAM,eAAe,CAAC,CAAA;AAQrC;IACI,0BAAY,OAAuB;QAC/B,IAAM,aAAa,GAAG;YAClB,KAAK,EAAE,EAAE;YACT,MAAM,EAAE,CAAC;YACT,KAAK,EAAE,CAAC;YACR,MAAM,EAAE,CAAC;YACT,OAAO,EAAE,CAAC;YACV,MAAM,EAAE,CAAC;YACT,SAAS,EAAE,CAAC;YACZ,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,GAAG;YACV,KAAK,EAAE,EAAE;YACT,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,KAAK;YACd,SAAS,EAAE,SAAS;YACpB,MAAM,EAAE,CAAC;YACT,QAAQ,EAAE,UAAU;SACvB,CAAC;QAEF,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAEtD,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3D,CAAC;IA3BL;QAAC,GAAG,CAAC,SAAS,CAAC;YACX,QAAQ,EAAE,YAAY;YACtB,QAAQ,EAAE,EAAE;SACf,CAAC;6CAE2B,UAAU;wBAFrC;IAyBF,uBAAC;AAAD,CAAC,AAxBD,IAwBC;AAxBY,wBAAgB,mBAwB5B,CAAA"}

40
src/PinkParrot/app/core/angular/spinner.component.ts

@ -0,0 +1,40 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
declare var Spinner: any;
@Ng2.Component({
selector: 'gp-spinner',
template: ''
})
export class SpinnerComponent {
constructor(element: Ng2.ElementRef) {
const mediumOptions = {
lines: 12,
length: 5,
width: 2,
radius: 6,
corners: 1,
rotate: 0,
direction: 1,
color: '#000',
speed: 1.5,
trail: 40,
shadow: false,
hwaccel: false,
className: 'spinner',
zIndex: 0,
position: 'relative'
};
element.nativeElement.classList.add('spinner-medium');
new Spinner(mediumOptions).spin(element.nativeElement);
}
}

1
src/PinkParrot/app/core/angular/user-report.component.js.map

@ -0,0 +1 @@
{"version":3,"file":"user-report.component.js","sourceRoot":"","sources":["user-report.component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;;;;;;;;;;AAEH,IAAY,GAAG,WAAM,eAAe,CAAC,CAAA;AAErC,+BAAiC,qBAAqB,CAAC,CAAA;AAMvD;IACI,6BAAY,MAAwB,EACxB,QAAiB,EAAE,GAAG,EAAC,QAAQ;QAAvC,wBAAyB,GAAzB,mBAAyB;QAAjB,aAAQ,GAAR,QAAQ,CAAS;QAEzB,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACrD,CAAC;IAEM,sCAAQ,GAAf;QACI,UAAU,CAAC;YACP,IAAM,GAAG,GAAG,0CAA0C,CAAC;YAEvD,IAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAChD,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC;YACjB,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;YAEpB,IAAM,IAAI,GAAG,QAAQ,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;YAExD,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC/C,CAAC,EAAE,IAAI,CAAC,CAAC;IACb,CAAC;IAxBL;QAAC,GAAG,CAAC,SAAS,CAAC;YACX,QAAQ,EAAE,gBAAgB;YAC1B,QAAQ,EAAE,EAAE;SACf,CAAC;;2BAAA;IAsBF,0BAAC;AAAD,CAAC,AArBD,IAqBC;AArBY,2BAAmB,sBAqB/B,CAAA"}

37
src/PinkParrot/app/core/angular/user-report.component.ts

@ -0,0 +1,37 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2 from '@angular/core';
import { UserReportConfig } from './../configurations';
@Ng2.Component({
selector: 'gp-user-report',
template: ''
})
export class UserReportComponent implements Ng2.OnInit {
constructor(config: UserReportConfig,
private readonly renderer: Ng2.Renderer
) {
window['_urq'] = window['_urq'] || [];
window['_urq'].push(['initSite', config.siteId]);
}
public ngOnInit() {
setTimeout(() => {
const url = 'https://cdn.userreport.com/userreport.js';
const script = document.createElement('script');
script.src = url;
script.async = true;
const node = document.getElementsByTagName('script')[0];
node.parentNode.insertBefore(script, node);
}, 4000);
}
}

1
src/PinkParrot/app/core/angular/validators.js.map

@ -0,0 +1 @@
{"version":3,"file":"validators.js","sourceRoot":"","sources":["validators.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;AAIH;IAAA;IAgBA,CAAC;IAfiB,kBAAO,GAArB,UAAsB,QAAgB,EAAE,QAAgB;QACpD,MAAM,CAAC,UAAC,OAAiC;YACrC,IAAI,CAAC,GAAW,OAAO,CAAC,KAAK,CAAC;YAE9B,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACxB,MAAM,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;YACpC,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;gBACtB,MAAM,CAAC,EAAE,UAAU,EAAE,EAAE,eAAe,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3E,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;gBACtB,MAAM,CAAC,EAAE,UAAU,EAAE,EAAE,eAAe,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3E,CAAC;YAED,MAAM,CAAC,EAAE,CAAC;QACd,CAAC,CAAC;IACN,CAAC;IACL,iBAAC;AAAD,CAAC,AAhBD,IAgBC;AAhBY,kBAAU,aAgBtB,CAAA"}

1
src/PinkParrot/app/core/angular/validators.spec.js.map

@ -0,0 +1 @@
{"version":3,"file":"validators.spec.js","sourceRoot":"","sources":["validators.spec.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;AAEH,IAAY,QAAQ,WAAM,gBAAgB,CAAC,CAAA;AAE3C,iBAA2B,OAAO,CAAC,CAAA;AAEnC,QAAQ,CAAC,YAAY,EAAE;IACnB,IAAI,eAAoB,CAAC;IAEzB,UAAU,CAAC;QACP,eAAe,GAAG,aAAU,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE;QACxC,IAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAE/C,IAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QAErC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,SAAS,EAAE,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE;QACnD,IAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAE1C,IAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QAErC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE;QACtD,IAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAE5C,IAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QAErC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE;QAChD,IAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAE3C,IAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QAErC,MAAM,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IAChC,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}

50
src/PinkParrot/app/core/angular/validators.spec.ts

@ -0,0 +1,50 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2Forms from '@angular/forms';
import { Validators } from './../';
describe('Validators', () => {
let validateBetween: any;
beforeEach(() => {
validateBetween = Validators.between(10, 200);
});
it('should return error when not a number', () => {
const input = new Ng2Forms.FormControl('text');
const error = validateBetween(input);
expect(error.validNumber).toBeFalsy();
});
it('should return error if less than minimum setting', () => {
const input = new Ng2Forms.FormControl(5);
const error = validateBetween(input);
expect(error.minValue).toBeDefined();
});
it('should return error if greater than maximum setting', () => {
const input = new Ng2Forms.FormControl(300);
const error = validateBetween(input);
expect(error.maxValue).toBeDefined();
});
it('should return empty value when value is valid', () => {
const input = new Ng2Forms.FormControl(50);
const error = validateBetween(input);
expect(error).toBeDefined();
});
});

26
src/PinkParrot/app/core/angular/validators.ts

@ -0,0 +1,26 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import * as Ng2Forms from '@angular/forms';
export class Validators {
public static between(minValue: number, maxValue: number) {
return (control: Ng2Forms.AbstractControl): { [key: string]: any } => {
let n: number = control.value;
if (typeof n !== 'number') {
return { 'validNumber': false };
} else if (n < minValue) {
return { 'minValue': { 'requiredValue': minValue, 'actualValue': n } };
} else if (n > maxValue) {
return { 'maxValue': { 'requiredValue': maxValue, 'actualValue': n } };
}
return {};
};
}
}

1
src/PinkParrot/app/core/configurations.js.map

@ -0,0 +1 @@
{"version":3,"file":"configurations.js","sourceRoot":"","sources":["configurations.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;AAEH;IACI,sBAAmB,QAAc,EAAE,MAAM;QAA7B,wBAAqB,GAArB,gBAAqB;QAAd,aAAQ,GAAR,QAAQ,CAAM;IAAY,CAAC;IAClD,mBAAC;AAAD,CAAC,AAFD,IAEC;AAFY,oBAAY,eAExB,CAAA;AAED;IACI,gCAAmB,QAAc,EAAE,MAAM;QAA7B,wBAAqB,GAArB,gBAAqB;QAAd,aAAQ,GAAR,QAAQ,CAAM;IAAY,CAAC;IAClD,6BAAC;AAAD,CAAC,AAFD,IAEC;AAFY,8BAAsB,yBAElC,CAAA;AAED;IACI,8BAAmB,QAAyB,EAAE,OAAO;QAAzC,wBAAgC,GAAhC,2BAAgC;QAAzB,aAAQ,GAAR,QAAQ,CAAiB;IAAa,CAAC;IAC9D,2BAAC;AAAD,CAAC,AAFD,IAEC;AAFY,4BAAoB,uBAEhC,CAAA;AAED;IACI,0BAAmB,QAAe,EAAE,MAAM;QAA9B,wBAAsB,GAAtB,iBAAsB;QAAf,aAAQ,GAAR,QAAQ,CAAO;IAAY,CAAC;IACnD,uBAAC;AAAD,CAAC,AAFD,IAEC;AAFY,wBAAgB,mBAE5B,CAAA;AAED;IACI,wBACW,QAAa,EAAE,MAAM,EACrB,QAAe,EAAE,MAAM,EACvB,QAAyB;QAFhC,wBAAoB,GAApB,eAAoB;QACpB,wBAAsB,GAAtB,iBAAsB;QACtB,wBAAgC,GAAhC,WAAgB,SAAS,GAAG,IAAI;QAFzB,aAAQ,GAAR,QAAQ,CAAK;QACb,aAAQ,GAAR,QAAQ,CAAO;QACf,aAAQ,GAAR,QAAQ,CAAiB;IAEpC,CAAC;IACL,qBAAC;AAAD,CAAC,AAPD,IAOC;AAPY,sBAAc,iBAO1B,CAAA"}

31
src/PinkParrot/app/core/configurations.ts

@ -0,0 +1,31 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export class ApiUrlConfig {
constructor(public readonly value: string) { }
}
export class DecimalSeparatorConfig {
constructor(public readonly value: string) { }
}
export class ProductionModeConfig {
constructor(public readonly isProductionMode: boolean) { }
}
export class UserReportConfig {
constructor(public readonly siteId: string) { }
}
export class CurrencyConfig {
constructor(
public readonly code: string,
public readonly symbol: string,
public readonly showAfter = true
) {
}
}

1
src/PinkParrot/app/core/index.js.map

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;;;;AAEH,iBAAc,kBAAkB,CAAC,EAAA;AACjC,iBAAc,sBAAsB,CAAC,EAAA;AACrC,iBAAc,kBAAkB,CAAC,EAAA;AACjC,iBAAc,aAAa,CAAC,EAAA;AAC5B,iBAAc,8BAA8B,CAAC,EAAA;AAC7C,iBAAc,yBAAyB,CAAC,EAAA;AACxC,iBAAc,gCAAgC,CAAC,EAAA;AAC/C,iBAAc,6BAA6B,CAAC,EAAA;AAC5C,iBAAc,0BAA0B,CAAC,EAAA;AACzC,iBAAc,eAAe,CAAC,EAAA;AAC9B,iBAAc,uBAAuB,CAAC,EAAA;AACtC,iBAAc,qBAAqB,CAAC,EAAA;AACpC,iBAAc,mBAAmB,CAAC,EAAA;AAClC,iBAAc,kBAAkB,CAAC,EAAA;AACjC,iBAAc,0BAA0B,CAAC,EAAA;AACzC,iBAAc,wBAAwB,CAAC,EAAA;AACvC,iBAAc,0BAA0B,CAAC,EAAA;AACzC,iBAAc,uBAAuB,CAAC,EAAA;AACtC,iBAAc,qBAAqB,CAAC,EAAA;AACpC,iBAAc,kBAAkB,CAAC,EAAA;AACjC,iBAAc,cAAc,CAAC,EAAA;AAC7B,iBAAc,eAAe,CAAC,EAAA"}

29
src/PinkParrot/app/core/index.ts

@ -0,0 +1,29 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export * from './angular/action';
export * from './angular/validators';
export * from './configurations';
export * from './plattform';
export * from './services/clipboard.service';
export * from './services/drag.service';
export * from './services/local-store.service';
export * from './services/shortcut.service';
export * from './services/title.service';
export * from './utils/color';
export * from './utils/color-palette';
export * from './utils/date-helper';
export * from './utils/date-time';
export * from './utils/duration';
export * from './utils/immutable-id-map';
export * from './utils/immutable-list';
export * from './utils/immutable-object';
export * from './utils/immutable-set';
export * from './utils/math-helper';
export * from './utils/rotation';
export * from './utils/vec2';
export * from './utils/rect2';

1
src/PinkParrot/app/core/plattform.js.map

@ -0,0 +1 @@
{"version":3,"file":"plattform.js","sourceRoot":"","sources":["plattform.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;AAIH;IAAA;IAIA,CAAC;IAAD,eAAC;AAAD,CAAC,AAJD,IAIC;AAJqB,gBAAQ,WAI7B,CAAA"}

14
src/PinkParrot/app/core/plattform.ts

@ -0,0 +1,14 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Observable } from 'rxjs/Observable';
export abstract class AppStore {
public abstract select<T>(pathOrMapFn: any, ...paths: string[]): Observable<T>;
public abstract next(action: any): void;
}

1
src/PinkParrot/app/core/services/clipboard.service.js.map

@ -0,0 +1 @@
{"version":3,"file":"clipboard.service.js","sourceRoot":"","sources":["clipboard.service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;AAEH,qBAA4C,MAAM,CAAC,CAAA;AAEtC,+BAAuB,GAAG;IACnC,MAAM,CAAC,IAAI,gBAAgB,EAAE,CAAC;AAClC,CAAC,CAAC;AAEF;IAAA;QACY,iBAAY,GAAG,IAAI,sBAAe,CAAS,EAAE,CAAC,CAAC;IAmB3D,CAAC;IAjBG,sBAAW,kCAAI;aAAf;YACI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC;QAC7B,CAAC;;;OAAA;IAEM,qCAAU,GAAjB;QACI,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,UAAA,CAAC;YACzB,MAAM,GAAG,CAAC,CAAC;QACf,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAEjB,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;IACxB,CAAC;IAEM,kCAAO,GAAd,UAAe,IAAS;QACpB,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;IACL,uBAAC;AAAD,CAAC,AApBD,IAoBC;AApBY,wBAAgB,mBAoB5B,CAAA"}

1
src/PinkParrot/app/core/services/clipboard.service.spec.js.map

@ -0,0 +1 @@
{"version":3,"file":"clipboard.service.spec.js","sourceRoot":"","sources":["clipboard.service.spec.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;;AAEH,iBAA0D,OAAO,CAAC,CAAA;AAElE,QAAQ,CAAC,iBAAiB,EAAE;IAExB,EAAE,CAAC,iCAAiC,EAAE;QAClC,IAAM,gBAAgB,GAAG,0BAAuB,EAAE,CAAC;QAEnD,MAAM,CAAC,gBAAgB,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE;QACrB,IAAM,gBAAgB,GAAG,IAAI,mBAAgB,EAAE,CAAC;QAEhD,MAAM,CAAC,gBAAgB,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE;QACnD,IAAM,gBAAgB,GAAG,IAAI,mBAAgB,EAAE,CAAC;QAEhD,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE;QAClC,IAAM,gBAAgB,GAAG,IAAI,mBAAgB,EAAE,CAAC;QAEhD,gBAAgB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAEtC,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE;QACzC,IAAM,gBAAgB,GAAG,IAAI,mBAAgB,EAAE,CAAC;QAEhD,IAAI,IAAI,GAAG,EAAE,CAAC;QAEd,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,UAAA,CAAC;YAC7B,IAAI,GAAG,CAAC,CAAC;QACb,CAAC,CAAC,CAAC;QAEH,gBAAgB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAEtC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}

51
src/PinkParrot/app/core/services/clipboard.service.spec.ts

@ -0,0 +1,51 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { ClipboardService, ClipboardServiceFactory } from './../';
describe('ShortcutService', () => {
it('should instantiate from factory', () => {
const clipboardService = ClipboardServiceFactory();
expect(clipboardService).toBeDefined();
});
it('should instantiate', () => {
const clipboardService = new ClipboardService();
expect(clipboardService).toBeDefined();
});
it('should return empty string if clipboard is empty', () => {
const clipboardService = new ClipboardService();
expect(clipboardService.selectText()).toBe('');
});
it('should get value from clipboard', () => {
const clipboardService = new ClipboardService();
clipboardService.setText('MyContent');
expect(clipboardService.selectText()).toBe('MyContent');
});
it('should raise subject when setting text', () => {
const clipboardService = new ClipboardService();
let text = '';
clipboardService.text.subscribe(t => {
text = t;
});
clipboardService.setText('MyContent');
expect(text).toBe('MyContent');
});
});

34
src/PinkParrot/app/core/services/clipboard.service.ts

@ -0,0 +1,34 @@
/*
* Athene Requirements Center
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { BehaviorSubject, Observable } from 'rxjs';
export const ClipboardServiceFactory = () => {
return new ClipboardService();
};
export class ClipboardService {
private textInstance = new BehaviorSubject<string>('');
public get text(): Observable<string> {
return this.textInstance;
}
public selectText(): string {
let result = '';
this.textInstance.subscribe(t => {
result = t;
}).unsubscribe();
return result || '';
}
public setText(text: any) {
this.textInstance.next(text);
}
}

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

Loading…
Cancel
Save