Browse Source

Cleanup

pull/1/head
Sebastian 10 years ago
parent
commit
59800b7f42
  1. 40
      src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs
  2. 9
      src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs
  3. 41
      src/Squidex.Infrastructure/CQRS/Events/WrongEventVersionException.cs
  4. 2
      src/Squidex.Infrastructure/DomainObjectVersionException.cs
  5. 2
      src/Squidex/Pipeline/WebpackMiddleware.cs
  6. 2
      src/Squidex/app/app.component.html
  7. 30
      src/Squidex/app/app.component.spec.ts
  8. 2
      src/Squidex/app/app.module.ts
  9. 16
      src/Squidex/app/features/apps/pages/apps-page.component.html
  10. 8
      src/Squidex/app/features/apps/pages/apps-page.component.scss
  11. 23
      src/Squidex/app/features/apps/pages/apps-page.component.ts
  12. 2
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.scss
  13. 65
      src/Squidex/app/framework/angular/action.spec.ts
  14. 72
      src/Squidex/app/framework/angular/action.ts
  15. 11
      src/Squidex/app/framework/angular/color-picker.component.html
  16. 68
      src/Squidex/app/framework/angular/color-picker.component.scss
  17. 117
      src/Squidex/app/framework/angular/color-picker.component.spec.ts
  18. 125
      src/Squidex/app/framework/angular/color-picker.component.ts
  19. 118
      src/Squidex/app/framework/angular/drag-model.directive.ts
  20. 128
      src/Squidex/app/framework/angular/image-drop.directive.ts
  21. 3
      src/Squidex/app/framework/angular/modal-view.directive.ts
  22. 3
      src/Squidex/app/framework/angular/panel-container.directive.ts
  23. 40
      src/Squidex/app/framework/angular/spinner.component.ts
  24. 14
      src/Squidex/app/framework/declarations.ts
  25. 12
      src/Squidex/app/framework/module.ts
  26. 42
      src/Squidex/app/framework/services/drag.service.spec.ts
  27. 30
      src/Squidex/app/framework/services/drag.service.ts
  28. 74
      src/Squidex/app/framework/utils/array-helper.spec.ts
  29. 74
      src/Squidex/app/framework/utils/array-helper.ts
  30. 16
      src/Squidex/app/framework/utils/color-palette.spec.ts
  31. 41
      src/Squidex/app/framework/utils/color-palette.ts
  32. 273
      src/Squidex/app/framework/utils/color.spec.ts
  33. 200
      src/Squidex/app/framework/utils/color.ts
  34. 237
      src/Squidex/app/framework/utils/rect2.spec.ts
  35. 191
      src/Squidex/app/framework/utils/rect2.ts
  36. 73
      src/Squidex/app/framework/utils/rotation.spec.ts
  37. 57
      src/Squidex/app/framework/utils/rotation.ts
  38. 166
      src/Squidex/app/framework/utils/vec2.spec.ts
  39. 127
      src/Squidex/app/framework/utils/vec2.ts
  40. 3
      src/Squidex/app/shared/components/dashboard-link.directive.ts
  41. 2
      src/Squidex/app/shared/services/auth.service.ts
  42. 17
      src/Squidex/app/shared/services/schemas.service.ts
  43. 18
      src/Squidex/app/shell/pages/app/left-menu.component.ts
  44. 13
      src/Squidex/app/shell/pages/home/home-page.component.ts
  45. 5
      src/Squidex/app/shell/pages/internal/apps-menu.component.ts
  46. 3
      src/Squidex/app/shell/pages/internal/internal-area.component.ts
  47. 3
      src/Squidex/app/shell/pages/internal/profile-menu.component.ts
  48. 2
      src/Squidex/app/shell/pages/not-found/not-found-page.component.html
  49. 15
      src/Squidex/app/shell/pages/not-found/not-found-page.component.ts
  50. 11
      src/Squidex/app/theme/_bootstrap.scss
  51. 2
      src/Squidex/wwwroot/index.html

40
src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs

@ -18,6 +18,7 @@ using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Reflection;
// ReSharper disable ClassNeverInstantiated.Local
// ReSharper disable UnusedMember.Local
// ReSharper disable InvertIf
namespace Squidex.Infrastructure.MongoDb.EventStore
{
@ -104,15 +105,11 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
public async Task AppendEventsAsync(Guid commitId, string streamName, int expectedVersion, IEnumerable<EventData> events)
{
var allCommits =
await Collection.Find(c => c.EventStream == streamName)
.Project<BsonDocument>(Projection.Include(x => x.EventCount))
.ToListAsync();
var currentVersion = await GetEventVersionAsync(streamName);
var currentVersion = allCommits.Sum(x => x["EventCount"].ToInt32()) - 1;
if (currentVersion != expectedVersion)
{
throw new InvalidOperationException($"Current version: {currentVersion}, expected version: {expectedVersion}");
throw new WrongEventVersionException(currentVersion, expectedVersion);
}
var now = DateTime.UtcNow;
@ -130,8 +127,37 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
{
commit.EventCount = commit.Events.Count;
await Collection.InsertOneAsync(commit);
try
{
await Collection.InsertOneAsync(commit);
}
catch (MongoWriteException e)
{
if (e.WriteError?.Category == ServerErrorCategory.DuplicateKey)
{
currentVersion = await GetEventVersionAsync(streamName);
if (currentVersion != expectedVersion)
{
throw new WrongEventVersionException(currentVersion, expectedVersion);
}
}
throw;
}
}
}
private async Task<int> GetEventVersionAsync(string streamName)
{
var allCommits =
await Collection.Find(c => c.EventStream == streamName)
.Project<BsonDocument>(Projection.Include(x => x.EventCount))
.ToListAsync();
var currentVersion = allCommits.Sum(x => x["EventCount"].ToInt32()) - 1;
return currentVersion;
}
}
}

9
src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs

@ -85,7 +85,14 @@ namespace Squidex.Infrastructure.CQRS.Commands
var eventsToSave = events.Select(x => formatter.ToEventData(x, commitId)).ToList();
await eventStore.AppendEventsAsync(commitId, streamName, versionExpected, eventsToSave);
try
{
await eventStore.AppendEventsAsync(commitId, streamName, versionExpected, eventsToSave);
}
catch (WrongEventVersionException e)
{
throw new DomainObjectVersionException(domainObject.Id.ToString(), domainObject.GetType(), versionCurrent, versionExpected);
}
foreach (var eventData in eventsToSave)
{

41
src/Squidex.Infrastructure/CQRS/Events/WrongEventVersionException.cs

@ -0,0 +1,41 @@
// ==========================================================================
// WrongEventVersionException.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
namespace Squidex.Infrastructure.CQRS.Events
{
public class WrongEventVersionException : Exception
{
private readonly int currentVersion;
private readonly int expectedVersion;
public int CurrentVersion
{
get { return currentVersion; }
}
public int ExpectedVersion
{
get { return expectedVersion; }
}
public WrongEventVersionException(int currentVersion, int expectedVersion)
: base(FormatMessage(currentVersion, expectedVersion))
{
this.currentVersion = currentVersion;
this.expectedVersion = expectedVersion;
}
private static string FormatMessage(int currentVersion, int expectedVersion)
{
return $"Requested version {expectedVersion}, but found {currentVersion}.";
}
}
}

2
src/Squidex.Infrastructure/DomainObjectVersionException.cs

@ -35,7 +35,7 @@ namespace Squidex.Infrastructure
private static string FormatMessage(string id, Type type, int currentVersion, int expectedVersion)
{
return $"Request version {expectedVersion} for object '{id}' (type {type}), but found {currentVersion}.";
return $"Requested version {expectedVersion} for object '{id}' (type {type}), but found {currentVersion}.";
}
}
}

2
src/Squidex/Pipeline/WebpackMiddleware.cs

@ -68,7 +68,7 @@ namespace Squidex.Pipeline
}
}
}
else
else if (context.Response.StatusCode != 304)
{
await buffer.CopyToAsync(body);
}

2
src/Squidex/app/app.component.html

@ -3,7 +3,7 @@
<div class="loading" *ngIf="!isLoaded">
<img src="/images/loader.gif" />
<div>Loading awesomeness</div>
<div>Loading Squidex</div>
</div>
</router-outlet>
</main>

30
src/Squidex/app/app.component.spec.ts

@ -1,30 +0,0 @@
import { TestBed } from '@angular/core/testing';
/* tslint:disable ordered-imports */
import { RouterModule, provideRoutes } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('App', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
imports: [
RouterModule,
RouterTestingModule
],
providers: [
provideRoutes([])
]
});
});
it('should work', () => {
const fixture = TestBed.createComponent(AppComponent);
expect(fixture.componentInstance instanceof AppComponent).toBe(true, 'should create AppComponent');
});
});

2
src/Squidex/app/app.module.ts

@ -21,7 +21,6 @@ import {
AuthService,
CurrencyConfig,
DecimalSeparatorConfig,
DragService,
HistoryService,
LanguageService,
LocalStoreService,
@ -78,7 +77,6 @@ export function configCurrency() {
AppsService,
AppMustExistGuard,
AuthService,
DragService,
HistoryService,
LanguageService,
LocalStoreService,

16
src/Squidex/app/features/apps/pages/apps-page.component.html

@ -1,9 +1,21 @@
<content>
<div class="empty">
<sqx-title message="Apps"></sqx-title>
<content>
<div class="empty" *ngIf="apps && apps.length === 0">
<h3 class="empty-headline">You are not collaborating to any app yet</h3>
<button class="apps-empty-button btn btn-success" (click)="modalDialog.show()"><i class="icon-plus"></i> Create New App</button>
</div>
<div class="apps">
<div class="app card" *ngFor="let app of apps">
<div class="card-block">
<h4 class="card-title">{{app.name}}</h4>
<a [routerLink]="['/app', app.name]">Edit</a>
</div>
</div>
</div>
</content>
<div class="modal" *sqxModalView="modalDialog">

8
src/Squidex/app/features/apps/pages/apps-page.component.scss

@ -11,7 +11,13 @@ content {
}
&-headline {
margin-bottom: 20px;
margin: 20px;
margin-top: 100px;
}
}
.app {
float: left;
width: 20rem;
margin: 10px;
}

23
src/Squidex/app/features/apps/pages/apps-page.component.ts

@ -5,12 +5,13 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, OnInit } from '@angular/core';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import {
AppDto,
AppsStoreService,
ModalView,
TitleService
ModalView
} from 'shared';
@Component({
@ -18,11 +19,14 @@ import {
styleUrls: ['./apps-page.component.scss'],
templateUrl: './apps-page.component.html'
})
export class AppsPageComponent implements OnInit {
export class AppsPageComponent implements OnInit, OnDestroy {
private appsSubscription: Subscription;
public modalDialog = new ModalView();
public apps: AppDto[];
constructor(
private readonly title: TitleService,
private readonly appsStore: AppsStoreService
) {
}
@ -30,6 +34,13 @@ export class AppsPageComponent implements OnInit {
public ngOnInit() {
this.appsStore.selectApp(null);
this.title.setTitle('Apps');
this.appsSubscription =
this.appsStore.apps.subscribe(apps => {
this.apps = apps || [];
});
}
public ngOnDestroy() {
this.appsSubscription.unsubscribe();
}
}

2
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.scss

@ -41,7 +41,7 @@
}
.icon-search {
@include absolute(8px, auto, auto, 12px);
@include absolute(10px, auto, auto, 12px);
color: $color-dark-foreground-selected;
font-size: 1.3rem;
font-weight: lighter;

65
src/Squidex/app/framework/angular/action.spec.ts

@ -1,65 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Subject } from 'rxjs';
import { Action } from './../';
class MockupObject {
public isDestroyCalled = false;
@Action()
public event1 = new Subject<string>().map(_ => { return { type: 'MOCK_ACTION' }; });
@Action()
public event2 = new Subject<string>().map(_ => { return { type: 'MOCK_ACTION' }; });
constructor(private readonly store: any) { }
public log() {
this.store.log();
}
public init() {
this.event2 = new Subject<string>().map(_ => { return { type: 'MOCK_ACTION' }; });
}
public ngOnDestroy() {
this.isDestroyCalled = true;
}
}
describe('Action', () => {
it('should make 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();
});
});

72
src/Squidex/app/framework/angular/action.ts

@ -1,72 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
/* tslint:disable:no-empty */
/* tslint:disable:only-arrow-functions */
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();
}
});
}
};
}

11
src/Squidex/app/framework/angular/color-picker.component.html

@ -1,11 +0,0 @@
<div class="btn-group" [class.open]="isOpen">
<button type="button" class="btn btn-secondary btn-sm" [disabled]="isDisabled" (click)="toggleOpen()" [style.background]="selectedColor.toString()"></button>
<div class="dropdown-menu dropdown-menu-{{dropdownSide}}">
<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>

68
src/Squidex/app/framework/angular/color-picker.component.scss

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

117
src/Squidex/app/framework/angular/color-picker.component.spec.ts

@ -1,117 +0,0 @@
/*
* Squidex Headless CMS
*
* @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.registerOnChange((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.registerOnChange((c: Color) => {
lastColor = c;
});
colorPicker.selectColor(selectedColor);
expect(lastColor).toBeNull();
});
it('should update selected color when writing value', () => {
const colorPicker = new ColorPickerComponent({ nativeElement: {} });
const selectedColor = Color.RED;
colorPicker.writeValue(selectedColor);
expect(colorPicker.selectedColor).toBe(selectedColor);
});
it('should update selected color with palette default if setting invalid color', () => {
const colorPicker = new ColorPickerComponent({ nativeElement: {} });
colorPicker.writeValue('invalid');
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.writeValue('invalid');
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();
});
});

125
src/Squidex/app/framework/angular/color-picker.component.ts

@ -1,125 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, ElementRef, forwardRef, HostListener, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Color } from './../utils/color';
import { ColorPalette } from './../utils/color-palette';
/* tslint:disable:no-empty */
const NOOP = () => { };
export const SQX_COLOR_PICKER_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ColorPickerComponent),
multi: true
};
@Component({
selector: 'sqx-color-picker',
styleUrls: ['./color-picker.component.scss'],
templateUrl: './color-picker.component.html',
providers: [SQX_COLOR_PICKER_CONTROL_VALUE_ACCESSOR]
})
export class ColorPickerComponent implements ControlValueAccessor {
private changeCallback: (value: any) => void = NOOP;
private touchedCallback: () => void = NOOP;
public selectedColor: Color = Color.BLACK;
@Input()
public palette = ColorPalette.colors();
@Input()
public dropdownSide: 'left';
@Input()
public isOpen = false;
@Input()
public isDisabled = false;
constructor(private readonly element: ElementRef) {
this.updateColor(Color.BLACK);
}
public writeValue(value: any) {
this.updateColor(value);
}
public setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled;
}
public registerOnChange(fn: any) {
this.changeCallback = fn;
}
public registerOnTouched(fn: any) {
this.touchedCallback = fn;
}
@HostListener('document:click', ['$event.target'])
public onClick(targetElement: any) {
const clickedInside = this.element.nativeElement.contains(targetElement);
if (!clickedInside) {
this.close();
}
}
public toggleOpen() {
if (this.isOpen) {
this.close();
} else {
this.open();
}
}
public open() {
this.isOpen = true;
}
public close() {
this.isOpen = false;
this.touchedCallback();
}
public selectColor(color: Color) {
this.updateParent(color);
this.updateColor(color);
this.close();
}
private updateParent(color: Color) {
if (this.selectedColor.ne(color)) {
this.changeCallback(color);
}
}
private updateColor(color: Color) {
let hasColor = false;
try {
this.selectedColor = Color.fromValue(color);
hasColor = true;
} catch (e) {
hasColor = false;
}
if (!hasColor || !this.selectedColor) {
if (this.palette) {
this.selectedColor = this.palette.defaultColor;
} else {
this.selectedColor = Color.BLACK;
}
}
}
}

118
src/Squidex/app/framework/angular/drag-model.directive.ts

@ -1,118 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Directive, ElementRef, HostListener, Input, Renderer }from '@angular/core';
import { DragService } from './../services/drag.service';
import { Vec2 } from './../utils/vec2';
@Directive({
selector: '[sqxDragModel]'
})
export class DragModelDirective {
private startOffset: Vec2;
private startPosition: Vec2;
private mouseMoveSubscription: Function | null;
private mouseUpSubscription: Function | null;
private clonedElement: HTMLElement | null;
@Input('sqxDragModel')
public model: any;
constructor(
private readonly element: ElementRef,
private readonly renderer: Renderer,
private readonly dragService: DragService
) {
}
@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('sqx-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);
}
}

128
src/Squidex/app/framework/angular/image-drop.directive.ts

@ -1,128 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Directive, ElementRef, HostListener } from '@angular/core';
import { DragService } from './../services/drag.service';
import { Vec2 } from './../utils/vec2';
@Directive({
selector: '.sqx-image-drop'
})
export class ImageDropDirective {
constructor(
private readonly element: ElementRef,
private readonly dragService: DragService
) {
}
@HostListener('dragenter', ['$event'])
public onDragEnter(event: DragDropEvent) {
this.tryStopEvent(event);
}
@HostListener('dragover', ['$event'])
public onDragOver(event: DragDropEvent) {
this.tryStopEvent(event);
}
@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;
/* tslint:disable:prefer-for-of */
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();
const 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);
};
export interface DragDropEvent extends MouseEvent {
readonly dataTransfer: DataTransfer;
}

3
src/Squidex/app/framework/angular/modal-view.directive.ts

@ -6,6 +6,7 @@
*/
import { Directive, EmbeddedViewRef, Input, OnChanges, OnDestroy, OnInit, Renderer, TemplateRef, ViewContainerRef } from '@angular/core';
import { Subscription } from 'rxjs';
import { ModalView } from './../utils/modal-view';
@ -13,7 +14,7 @@ import { ModalView } from './../utils/modal-view';
selector: '[sqxModalView]'
})
export class ModalViewDirective implements OnChanges, OnInit, OnDestroy {
private subscription: any | null;
private subscription: Subscription;
private isEnabled = true;
private clickHandler: Function | null;
private renderedView: EmbeddedViewRef<any> | null;

3
src/Squidex/app/framework/angular/panel-container.directive.ts

@ -6,6 +6,7 @@
*/
import { Directive, ElementRef, HostListener, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { PanelService } from './../services/panel.service';
@ -13,7 +14,7 @@ import { PanelService } from './../services/panel.service';
selector: '.panel-container'
})
export class PanelContainerDirective implements OnInit, OnDestroy {
private subscription: any;
private subscription: Subscription;
private panelsSize: number | null = null;
constructor(

40
src/Squidex/app/framework/angular/spinner.component.ts

@ -1,40 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, ElementRef } from '@angular/core';
declare var Spinner: any;
@Component({
selector: 'sqx-spinner',
template: ''
})
export class SpinnerComponent {
constructor(element: 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);
}
}

14
src/Squidex/app/framework/declarations.ts

@ -5,19 +5,15 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export * from './angular/action';
export * from './angular/animations';
export * from './angular/autocomplete.component';
export * from './angular/validators';
export * from './angular/cloak.directive';
export * from './angular/color-picker.component';
export * from './angular/copy.directive';
export * from './angular/date-time.pipes';
export * from './angular/drag-model.directive';
export * from './angular/focus-on-change.directive';
export * from './angular/focus-on-init.directive';
export * from './angular/http-utils';
export * from './angular/image-drop.directive';
export * from './angular/modal-view.directive';
export * from './angular/money.pipe';
export * from './angular/panel.directive';
@ -25,14 +21,12 @@ export * from './angular/panel-container.directive';
export * from './angular/scroll-active.directive';
export * from './angular/shortcut.component';
export * from './angular/slider.component';
export * from './angular/spinner.component';
export * from './angular/tag-editor.component';
export * from './angular/title.component';
export * from './angular/user-report.component';
export * from './configurations';
export * from './services/clipboard.service';
export * from './services/drag.service';
export * from './services/local-store.service';
export * from './services/message-bus';
export * from './services/notification.service';
@ -42,15 +36,9 @@ export * from './services/title.service';
export * from './plattform';
export * from './utils/array-helper';
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-array';
export * from './utils/math-helper';
export * from './utils/modal-view';
export * from './utils/rotation';
export * from './utils/vec2';
export * from './utils/rect2';
export * from './utils/modal-view';

12
src/Squidex/app/framework/module.ts

@ -14,16 +14,13 @@ import { RouterModule } from '@angular/router';
import {
AutocompleteComponent,
CloakDirective,
ColorPickerComponent,
CopyDirective,
DayOfWeekPipe,
DayPipe,
DragModelDirective,
DurationPipe,
FocusOnChangeDirective,
FocusOnInitDirective,
FromNowPipe,
ImageDropDirective,
ModalViewDirective,
MoneyPipe,
MonthPipe,
@ -34,7 +31,6 @@ import {
ShortDatePipe,
ShortTimePipe,
SliderComponent,
SpinnerComponent,
TagEditorComponent,
TitleComponent,
UserReportComponent
@ -51,16 +47,13 @@ import {
declarations: [
AutocompleteComponent,
CloakDirective,
ColorPickerComponent,
CopyDirective,
DayOfWeekPipe,
DayPipe,
DragModelDirective,
DurationPipe,
FocusOnChangeDirective,
FocusOnInitDirective,
FromNowPipe,
ImageDropDirective,
ModalViewDirective,
MoneyPipe,
MonthPipe,
@ -71,7 +64,6 @@ import {
ShortDatePipe,
ShortTimePipe,
SliderComponent,
SpinnerComponent,
TagEditorComponent,
TitleComponent,
UserReportComponent
@ -79,16 +71,13 @@ import {
exports: [
AutocompleteComponent,
CloakDirective,
ColorPickerComponent,
CopyDirective,
DayOfWeekPipe,
DayPipe,
DragModelDirective,
DurationPipe,
FocusOnChangeDirective,
FocusOnInitDirective,
FromNowPipe,
ImageDropDirective,
ModalViewDirective,
MoneyPipe,
MonthPipe,
@ -99,7 +88,6 @@ import {
ShortDatePipe,
ShortTimePipe,
SliderComponent,
SpinnerComponent,
TagEditorComponent,
TitleComponent,
UserReportComponent,

42
src/Squidex/app/framework/services/drag.service.spec.ts

@ -1,42 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import {
DragService,
DragServiceFactory,
DropEvent
} from './../';
describe('DragService', () => {
it('should instantiate from factory', () => {
const dragService = DragServiceFactory();
expect(dragService).toBeDefined();
});
it('should instantiate', () => {
const dragService = new DragService();
expect(dragService).toBeDefined();
});
it('should raise event handler when dropped', () => {
let emittedEvent: DropEvent | null = null;
const dragService = new DragService();
dragService.onDrop.subscribe(e => {
emittedEvent = e;
});
const event: DropEvent = { position: null!, model: null!, dropTarget: null! };
dragService.emitDrop(event);
expect(emittedEvent).toBe(event);
});
});

30
src/Squidex/app/framework/services/drag.service.ts

@ -1,30 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { Vec2 } from './../utils/vec2';
export interface DropEvent { position: Vec2; model: any; dropTarget: string; }
export const DragServiceFactory = () => {
return new DragService();
};
@Injectable()
export class DragService {
private readonly dropEvent = new Subject<DropEvent>();
public get onDrop(): Observable<DropEvent> {
return this.dropEvent;
}
public emitDrop(event: DropEvent) {
this.dropEvent.next(event);
}
}

74
src/Squidex/app/framework/utils/array-helper.spec.ts

@ -1,74 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { ArrayHelper } from './../';
describe('ArrayHelper', () => {
it('should push item', () => {
const oldArray = [1, 2, 3];
const newArray = ArrayHelper.push(oldArray, 4);
expect(newArray).toEqual([1, 2, 3, 4]);
});
it('should remove item if found', () => {
const oldArray = [1, 2, 3];
const newArray = ArrayHelper.remove(oldArray, 2);
expect(newArray).toEqual([1, 3]);
});
it('should not remove item if not found', () => {
const oldArray = [1, 2, 3];
const newArray = ArrayHelper.remove(oldArray, 5);
expect(newArray).toEqual([1, 2, 3]);
});
it('should remove all by predicate', () => {
const oldArray: number[] = [1, 2, 3, 4];
const newArray = ArrayHelper.removeAll(oldArray, (i: number) => i % 2 === 0);
expect(newArray).toEqual([1, 3]);
});
it('should return original if nothing has been removed', () => {
const oldArray: number[] = [1, 2, 3, 4];
const newArray = ArrayHelper.removeAll(oldArray, (i: number) => i % 200 === 0);
expect(newArray).toEqual(oldArray);
});
it('should replace item if found', () => {
const oldArray = [1, 2, 3];
const newArray = ArrayHelper.replace(oldArray, 2, 4);
expect(newArray).toEqual([1, 4, 3]);
});
it('should not replace item if not found', () => {
const oldArray = [1, 2, 3];
const newArray = ArrayHelper.replace(oldArray, 5, 5);
expect(newArray).toEqual([1, 2, 3]);
});
it('should replace all by predicate', () => {
const oldArray: number[] = [1, 2, 3, 4];
const newArray = ArrayHelper.replaceAll(oldArray, (i: number) => i % 2 === 0, i => i * 2);
expect(newArray).toEqual([1, 4, 3, 8]);
});
it('should return original if nothing has been replace', () => {
const oldArray: number[] = [1, 2, 3, 4];
const newArray = ArrayHelper.replaceAll(oldArray, (i: number) => i % 200 === 0, i => i);
expect(newArray).toEqual(oldArray);
});
});

74
src/Squidex/app/framework/utils/array-helper.ts

@ -1,74 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export module ArrayHelper {
export function push<T>(array: T[], item: T): T[] {
return [...array, item];
}
export function remove<T>(array: T[], item: T): T[] {
const index = array.indexOf(item);
if (index >= 0) {
return [...array.slice(0, index), ...array.slice(index + 1)];
} else {
return array;
}
}
export function removeAll<T>(array: T[], predicate: (item: T, index: number) => boolean): T[] {
const copy = array.slice();
let hasChange = false;
for (let i = 0; i < copy.length; ) {
if (predicate(copy[i], i)) {
copy.splice(i, 1);
hasChange = true;
} else {
++i;
}
}
return hasChange ? copy : array;
}
export function replace<T>(array: T[], oldItem: T, newItem: T): T[] {
const index = array.indexOf(oldItem);
if (index >= 0) {
const copy = array.slice();
copy[index] = newItem;
return copy;
} else {
return array;
}
}
export function replaceAll<T>(array: T[], predicate: (item: T, index: number) => boolean, replacer: (item: T) => T): T[] {
const copy = array.slice();
let hasChange = false;
for (let i = 0; i < copy.length; i++) {
if (predicate(copy[i], i)) {
const newItem = replacer(copy[i]);
if (copy[i] !== newItem) {
copy[i] = newItem;
hasChange = true;
}
}
}
return hasChange ? copy : array;
}
}

16
src/Squidex/app/framework/utils/color-palette.spec.ts

@ -1,16 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { ColorPalette } from './../';
describe('ColorPalatte', () => {
it('should generate colors', () => {
const palette = ColorPalette.colors();
expect(palette.colors.length).toBeGreaterThan(20);
});
});

41
src/Squidex/app/framework/utils/color-palette.ts

@ -1,41 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Color } from './color';
const BREWERS = {
greys: [0xffffff, 0xF0F0F0, 0xbdbdbd, 0x969696, 0x737373, 0x525252, 0x252525, 0x000000],
oranges: [0xfee6ce, 0xfdd0a2, 0xfdae6b, 0xfd8d3c, 0xf16913, 0xd94801, 0xa63603, 0x7f2704],
reds: [0xfee0d2, 0xfcbba1, 0xfc9272, 0xfb6a4a, 0xef3b2c, 0xcb181d, 0xa50f15, 0x67000d],
greens: [0xe5f5e0, 0xc7e9c0, 0xa1d99b, 0x74c476, 0x41ab5d, 0x238b45, 0x006d2c, 0x00441b],
purples: [0xefedf5, 0xdadaeb, 0xbcbddc, 0x9e9ac8, 0x807dba, 0x6a51a3, 0x54278f, 0x3f007d],
blues: [0xdeebf7, 0xc6dbef, 0x9ecae1, 0x6baed6, 0x4292c6, 0x2171b5, 0x08519c, 0x08306b]
};
export class ColorPalette {
constructor(
public readonly colors: Color[],
public readonly defaultColor: Color
) {
}
public static colors(): ColorPalette {
const colors: Color[] = [];
for (let key in BREWERS) {
if (BREWERS.hasOwnProperty(key)) {
const brewer = BREWERS[key];
for (let color of brewer) {
colors.push(Color.fromNumber(color));
}
}
}
return new ColorPalette(colors, colors[7]);
}
}

273
src/Squidex/app/framework/utils/color.spec.ts

@ -1,273 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Color } from './../';
describe('Color', () => {
it('should instantiate', () => {
const color = new Color(0.3, 0.6, 0.9);
expect(color.r).toBe(0.3);
expect(color.g).toBe(0.6);
expect(color.b).toBe(0.9);
});
it('should adjust values when instantiating', () => {
const color = new Color(-1, 0.5, 2);
expect(color.r).toBe(0);
expect(color.g).toBe(0.5);
expect(color.b).toBe(1);
});
it('should convert to color', () => {
const color = Color.fromHex(23, 46, 59);
expect(color.toNumber()).toBe(0x234659);
});
it('should convert to string', () => {
const color = Color.fromHex(23, 46, 59);
expect(color.toString()).toBe('#234659');
});
it('should convert to string with leading zeros', () => {
const color = Color.fromHex(3, 6, 9);
expect(color.toString()).toBe('#030609');
});
it('should be created from long string', () => {
const color = Color.fromValue('#336699');
expect(color.r).toBe(0.2);
expect(color.g).toBe(0.4);
expect(color.b).toBe(0.6);
});
it('should be created from short string', () => {
const color = Color.fromValue('#369');
expect(color.r).toBe(0.2);
expect(color.g).toBe(0.4);
expect(color.b).toBe(0.6);
});
it('should be created from rgb string', () => {
const color = Color.fromValue('rgb(51, 102, 153)');
expect(color.r).toBe(0.2);
expect(color.g).toBe(0.4);
expect(color.b).toBe(0.6);
});
it('should be created from rgb number', () => {
const color = Color.fromValue(0x336699);
expect(color.r).toBe(0.2);
expect(color.g).toBe(0.4);
expect(color.b).toBe(0.6);
});
it('should be created from rgb values', () => {
const color = Color.fromHex(33, 66, 99);
expect(color.r).toBe(0.2);
expect(color.g).toBe(0.4);
expect(color.b).toBe(0.6);
});
it('should convert from hsl with black', () => {
const color = Color.fromHsl(0, 0, 0);
expect(color.r).toBe(0);
expect(color.g).toBe(0);
expect(color.b).toBe(0);
});
it('should convert from hsl with dark black', () => {
const color = Color.fromHsl(0, 1, 0.1);
expect(color.r).toBe(0.2);
expect(color.g).toBe(0);
expect(color.b).toBe(0);
});
it('should convert from hsl with red', () => {
const color = Color.fromHsl(0, 1, 0.5);
expect(color.r).toBeCloseTo(1, 1);
expect(color.g).toBe(0);
expect(color.b).toBe(0);
});
it('should convert from hsl with yellow', () => {
const color = Color.fromHsl(60, 1, 0.5);
expect(color.r).toBeCloseTo(1, 1);
expect(color.g).toBeCloseTo(1, 1);
expect(color.b).toBe(0);
});
it('should convert from hsl with blue', () => {
const color = Color.fromHsl(120, 1, 0.5);
expect(color.r).toBe(0);
expect(color.g).toBe(1);
expect(color.b).toBe(0);
});
it('should convert from hsl with turkis', () => {
const color = Color.fromHsl(180, 1, 0.5);
expect(color.r).toBe(0);
expect(color.g).toBeCloseTo(1, 1);
expect(color.b).toBeCloseTo(1, 1);
});
it('should convert from hsv with red', () => {
const color = Color.fromHsv(240, 1, 1);
expect(color.r).toBe(0);
expect(color.g).toBe(0);
expect(color.b).toBe(1);
});
it('should convert from hsv with pink', () => {
const color = Color.fromHsv(300, 1, 1);
expect(color.r).toBe(1);
expect(color.g).toBe(0);
expect(color.b).toBe(1);
});
it('should convert from hsl with another red', () => {
const color = Color.fromHsl(360, 1, 0.5);
expect(color.r).toBeCloseTo(1, 1);
expect(color.g).toBe(0);
expect(color.b).toBe(0);
});
it('should convert from hsv with red', () => {
const color = Color.fromHsv(0, 1, 1);
expect(color.r).toBe(1);
expect(color.g).toBe(0);
expect(color.b).toBe(0);
});
it('should convert from hsv with yellow', () => {
const color = Color.fromHsv(60, 1, 1);
expect(color.r).toBe(1);
expect(color.g).toBe(1);
expect(color.b).toBe(0);
});
it('should convert from hsv with blue', () => {
const color = Color.fromHsv(120, 1, 1);
expect(color.r).toBe(0);
expect(color.g).toBe(1);
expect(color.b).toBe(0);
});
it('should convert from hsv with turkis', () => {
const color = Color.fromHsv(180, 1, 1);
expect(color.r).toBe(0);
expect(color.g).toBe(1);
expect(color.b).toBe(1);
});
it('should convert from hsv with red', () => {
const color = Color.fromHsv(240, 1, 1);
expect(color.r).toBe(0);
expect(color.g).toBe(0);
expect(color.b).toBe(1);
});
it('should convert from hsv with pink', () => {
const color = Color.fromHsv(300, 1, 1);
expect(color.r).toBe(1);
expect(color.g).toBe(0);
expect(color.b).toBe(1);
});
it('should be valid black', () => {
const color = Color.BLACK;
expect(color.r).toBe(0);
expect(color.g).toBe(0);
expect(color.b).toBe(0);
});
it('should be valid white', () => {
const color = Color.WHITE;
expect(color.r).toBe(1);
expect(color.g).toBe(1);
expect(color.b).toBe(1);
});
it('should be valid red', () => {
const color = Color.RED;
expect(color.r).toBe(1);
expect(color.g).toBe(0);
expect(color.b).toBe(0);
});
it('should be valid green', () => {
const color = Color.GREEN;
expect(color.r).toBe(0);
expect(color.g).toBe(1);
expect(color.b).toBe(0);
});
it('should be valid blue', () => {
const color = Color.BLUE;
expect(color.r).toBe(0);
expect(color.g).toBe(0);
expect(color.b).toBe(1);
});
it('should calculate correct luminance', () => {
expect(new Color(1, 1, 1).luminance).toBe(1);
expect(new Color(0, 0, 0).luminance).toBe(0);
expect(new Color(1, 0, 0).luminance).toBe(1 / 3);
expect(new Color(0, 1, 0).luminance).toBe(1 / 2);
expect(new Color(0, 0, 1).luminance).toBe(1 / 6);
});
it('should make valid equal comparisons', () => {
expect(new Color(0.1, 0.1, 0.1).eq(new Color(0.1, 0.1, 0.1))).toBeTruthy();
expect(new Color(0.1, 0.1, 0.4).eq(new Color(0.1, 0.1, 0.1))).toBeFalsy();
});
it('should make valid not equal comparisons', () => {
expect(new Color(0.1, 0.1, 0.1).ne(new Color(0.1, 0.1, 0.4))).toBeTruthy();
expect(new Color(0.1, 0.1, 0.1).ne(new Color(0.1, 0.1, 0.1))).toBeFalsy();
});
it('should return color when creating from color', () => {
const color = Color.fromHsv(300, 1, 1);
const created = Color.fromValue(color);
expect(created).toBe(color);
});
it('should throw error for invalid string', () => {
expect(() => Color.fromValue('INVALID')).toThrowError('Color is not in a valid format.');
});
});

200
src/Squidex/app/framework/utils/color.ts

@ -1,200 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
/* tslint:disable:no-bitwise */
interface IColorDefinition {
regex: RegExp;
process(bots: RegExpExecArray): Color;
}
const ColorDefinitions: IColorDefinition[] = [
{
regex: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
process: bits => new Color(
parseInt(bits[1], 10) / 255,
parseInt(bits[2], 10) / 255,
parseInt(bits[3], 10) / 255)
},
{
regex: /^(\w{2})(\w{2})(\w{2})$/,
process: bits => new Color(
parseInt(bits[1], 16) / 255,
parseInt(bits[2], 16) / 255,
parseInt(bits[3], 16) / 255)
},
{
regex: /^(\w{1})(\w{1})(\w{1})$/,
process: bits => new Color(
parseInt(bits[1] + bits[1], 16) / 255,
parseInt(bits[2] + bits[2], 16) / 255,
parseInt(bits[3] + bits[3], 16) / 255)
}
];
export class Color {
public static readonly BLACK = new Color(0, 0, 0);
public static readonly WHITE = new Color(1, 1, 1);
public static readonly GREEN = new Color(0, 1, 0);
public static readonly BLUE = new Color(0, 0, 1);
public static readonly RED = new Color(1, 0, 0);
public readonly r: number;
public readonly g: number;
public readonly b: number;
public get luminance(): number {
return (this.r + this.r + this.b + this.g + this.g + this.g) / 6;
}
constructor(r: number, g: number, b: number) {
this.r = Math.min(1, Math.max(0, r));
this.g = Math.min(1, Math.max(0, g));
this.b = Math.min(1, Math.max(0, b));
Object.freeze(this);
}
public eq(v: Color): boolean {
return this.r === v.r && this.g === v.g && this.b === v.b;
}
public ne(v: Color): boolean {
return this.r !== v.r || this.g !== v.g || this.b !== v.b;
}
public toNumber(): number {
return ((this.r * 255) << 16) + ((this.g * 255) << 8) + (this.b * 255);
}
public toString(): string {
let r = Math.round(this.r * 255).toString(16);
let g = Math.round(this.g * 255).toString(16);
let b = Math.round(this.b * 255).toString(16);
if (r.length === 1) {
r = '0' + r;
}
if (g.length === 1) {
g = '0' + g;
}
if (b.length === 1) {
b = '0' + b;
}
return '#' + r + g + b;
}
public static fromHex(r: number, g: number, b: number): Color {
return new Color(
parseInt('' + r, 16) / 255,
parseInt('' + g, 16) / 255,
parseInt('' + b, 16) / 255);
}
public static fromNumber(rgb: number): Color {
return new Color(
((rgb >> 16) & 0xff) / 255,
((rgb >> 8) & 0xff) / 255,
((rgb >> 0) & 0xff) / 255);
}
public static fromValue(value: number | string | Color): Color {
if (typeof value === 'string') {
return Color.fromString(value);
} else if (typeof value === 'number') {
return Color.fromNumber(value);
} else {
return value;
}
}
public static fromString(value: string) {
if (value.charAt(0) === '#') {
value = value.substr(1, 6);
}
value = value.replace(/ /g, '').toLowerCase();
for (let colorDefinition of ColorDefinitions) {
const bits = colorDefinition.regex.exec(value);
if (bits) {
return colorDefinition.process(bits);
}
}
throw new Error('Color is not in a valid format.');
}
public static fromHsv(h: number, s: number, v: number): Color {
const hi = Math.floor(h / 60) % 6;
const f = (h / 60) - Math.floor(h / 60);
v = v * 255;
const p = (v * (1 - s));
const q = (v * (1 - (f * s)));
const t = (v * (1 - ((1 - f) * s)));
switch (hi) {
case 0:
return new Color(v, t, p);
case 1:
return new Color(q, v, p);
case 2:
return new Color(p, v, t);
case 3:
return new Color(p, q, v);
case 4:
return new Color(t, p, v);
default:
return new Color(v, p, q);
}
}
public static fromHsl(h: number, s: number, l: number): Color {
let r = 0, g = 0, b = 0;
h = h / 360;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p: number, q: number, t: number) => {
if (t < 0) {
t += 1;
}
if (t > 1) {
t -= 1;
}
if (t < 1 / 6) {
return p + (q - p) * 6 * t;
}
if (t < 1 / 2) {
return q;
}
if (t < 2 / 3) {
return p + (q - p) * (2 / 3 - t) * 6;
}
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return new Color(r, g, b);
}
}

237
src/Squidex/app/framework/utils/rect2.spec.ts

@ -1,237 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import {
Rect2,
Rotation,
Vec2
} from './../';
describe('Rect2', () => {
it('should provide values from constructor', () => {
const rect = new Rect2(new Vec2(10, 20), new Vec2(50, 30));
expect(rect.x).toBe(10);
expect(rect.y).toBe(20);
expect(rect.top).toBe(20);
expect(rect.left).toBe(10);
expect(rect.right).toBe(60);
expect(rect.width).toBe(50);
expect(rect.height).toBe(30);
expect(rect.bottom).toBe(50);
expect(rect.centerX).toBe(35);
expect(rect.centerY).toBe(35);
expect(rect.size).toEqual(new Vec2(50, 30));
expect(rect.center).toEqual(new Vec2(35, 35));
expect(rect.position).toEqual(new Vec2(10, 20));
expect(rect.area).toBe(1500);
expect(rect.toString()).toBe('(x: 10, y: 20, w: 50, h: 30)');
});
it('should calculate isEmpty correctly', () => {
expect(new Rect2(new Vec2(10, 20), new Vec2(50, 30)).isEmpty).toBeFalsy();
expect(new Rect2(new Vec2(10, 20), new Vec2(-1, 30)).isEmpty).toBeTruthy();
expect(new Rect2(new Vec2(10, 20), new Vec2(50, -9)).isEmpty).toBeTruthy();
});
it('should calculate isInfinite correctly', () => {
expect(new Rect2(new Vec2(10, 20), new Vec2(50, 30)).isInfinite).toBeFalsy();
expect(new Rect2(new Vec2(10, 20), new Vec2(Number.POSITIVE_INFINITY, 30)).isInfinite).toBeTruthy();
expect(new Rect2(new Vec2(10, 20), new Vec2(50, Number.POSITIVE_INFINITY)).isInfinite).toBeTruthy();
});
it('should inflate correctly', () => {
const rect = new Rect2(new Vec2(10, 20), new Vec2(50, 30));
const actual1 = rect.inflateV(new Vec2(10, 20));
const actual2 = rect.inflate(10, 20);
const expected = new Rect2(new Vec2(0, 0), new Vec2(70, 70));
expect(actual1).toEqual(expected);
expect(actual2).toEqual(expected);
});
it('should deflate correctly', () => {
const rect = new Rect2(new Vec2(10, 20), new Vec2(50, 30));
const actual1 = rect.deflateV(new Vec2(25, 15));
const actual2 = rect.deflate(25, 15);
const expected = new Rect2(new Vec2(35, 35), new Vec2(0, 0));
expect(actual1).toEqual(expected);
expect(actual2).toEqual(expected);
});
it('should return true for intersection with infinite rect', () => {
const rect = new Rect2(new Vec2(10, 20), new Vec2(50, 30));
expect(rect.intersectsWith(Rect2.INFINITE)).toBeTruthy();
expect(Rect2.INFINITE.intersectsWith(rect)).toBeTruthy();
expect(Rect2.INFINITE.intersectsWith(Rect2.INFINITE)).toBeTruthy();
});
it('should return false for intersection with empty rect', () => {
const rect = new Rect2(new Vec2(10, 20), new Vec2(50, 30));
expect(rect.intersectsWith(Rect2.EMPTY)).toBeFalsy();
expect(Rect2.EMPTY.intersectsWith(rect)).toBeFalsy();
expect(Rect2.EMPTY.intersectsWith(Rect2.INFINITE)).toBeFalsy();
});
it('should return empty for no intersection', () => {
const rect = new Rect2(new Vec2(10, 20), new Vec2(50, 30));
const outer = new Rect2(new Vec2(100, 20), new Vec2(50, 30));
const actual = rect.intersect(outer);
const expected = Rect2.EMPTY;
expect(actual).toEqual(expected);
});
it('should return result for intersection', () => {
const rect = new Rect2(new Vec2(10, 20), new Vec2(50, 30));
const inner = new Rect2(new Vec2(35, 35), new Vec2(100, 30));
const actual = rect.intersect(inner);
const expected = new Rect2(new Vec2(35, 35), new Vec2(25, 15));
expect(actual).toEqual(expected);
});
it('should make correct contains by vector tests', () => {
const rect = new Rect2(new Vec2(10, 20), new Vec2(50, 30));
expect(rect.containsVec(rect.center)).toBeTruthy();
expect(rect.containsVec(new Vec2(rect.left, rect.top))).toBeTruthy();
expect(rect.containsVec(new Vec2(rect.centerX, 0))).toBeFalsy();
expect(rect.containsVec(new Vec2(rect.centerX, 100))).toBeFalsy();
expect(rect.containsVec(new Vec2(100, rect.centerY))).toBeFalsy();
expect(rect.containsVec(new Vec2(-50, rect.centerY))).toBeFalsy();
});
it('should return true when rect contains other rect', () => {
const rect = new Rect2(new Vec2(400, 400), new Vec2(400, 400));
const other = new Rect2(new Vec2(500, 500), new Vec2(200, 200));
expect(rect.containsRect(other)).toBeTruthy();
});
it('should return false when other rect is too top', () => {
const rect = new Rect2(new Vec2(400, 400), new Vec2(400, 400));
const other = new Rect2(new Vec2(300, 900), new Vec2(300, 100));
expect(rect.containsRect(other)).toBeFalsy();
});
it('should return false when other rect is too bottom', () => {
const rect = new Rect2(new Vec2(400, 400), new Vec2(400, 400));
const other = new Rect2(new Vec2(300, 900), new Vec2(100, 300));
expect(rect.containsRect(other)).toBeFalsy();
});
it('should return false when other rect is too left', () => {
const rect = new Rect2(new Vec2(400, 400), new Vec2(400, 400));
const other = new Rect2(new Vec2(200, 200), new Vec2(100, 300));
expect(rect.containsRect(other)).toBeFalsy();
});
it('should return false when other right is too left', () => {
const rect = new Rect2(new Vec2(400, 400), new Vec2(400, 400));
const other = new Rect2(new Vec2(900, 200), new Vec2(100, 300));
expect(rect.containsRect(other)).toBeFalsy();
});
it('should return empty when creating from null vectors', () => {
const actual = Rect2.createFromVecs(null);
const expected = Rect2.EMPTY;
expect(actual).toEqual(expected);
});
it('should return empty when creating from null rects', () => {
const actual = Rect2.createFromRects(null);
const expected = Rect2.EMPTY;
expect(actual).toEqual(expected);
});
it('should provide valid zero instance', () => {
const actual = Rect2.ZERO;
const expected = new Rect2(new Vec2(0, 0), new Vec2(0, 0));
expect(actual).toEqual(expected);
});
it('should provide valid empty instance', () => {
const actual = Rect2.EMPTY;
const expected = new Rect2(Vec2.NEGATIVE_INFINITE, Vec2.NEGATIVE_INFINITE);
expect(actual).toEqual(expected);
});
it('should provide valid infinite instance', () => {
const actual = Rect2.INFINITE;
const expected = new Rect2(Vec2.POSITIVE_INFINITE, Vec2.POSITIVE_INFINITE);
expect(actual).toEqual(expected);
});
it('should create correct rect from vectors', () => {
const actual =
Rect2.createFromVecs([
new Vec2(100, 100),
new Vec2(500, 300),
new Vec2(900, 800)]);
const expected = new Rect2(new Vec2(100, 100), new Vec2(800, 700));
expect(actual).toEqual(expected);
});
it('should create correct rect from rects', () => {
const actual =
Rect2.createFromRects([
new Rect2(new Vec2(100, 100), new Vec2(100, 100)),
new Rect2(new Vec2(500, 300), new Vec2(100, 100)),
new Rect2(new Vec2(150, 150), new Vec2(750, 650))]);
const expected = new Rect2(new Vec2(100, 100), new Vec2(800, 700));
expect(actual).toEqual(expected);
});
it('should create rect from rotation', () => {
const actual = Rect2.createRotated(new Vec2(400, 300), new Vec2(600, 400), Rotation.createFromRadian(Math.PI / 2));
const expected = new Rect2(new Vec2(500, 200), new Vec2(400, 600));
expect(actual).toEqual(expected);
});
it('should create rect from zero rotation', () => {
const actual = Rect2.createRotated(new Vec2(400, 300), new Vec2(600, 400), Rotation.ZERO);
const expected = new Rect2(new Vec2(400, 300), new Vec2(600, 400));
expect(actual).toEqual(expected);
});
it('should make valid equal comparisons', () => {
expect(new Rect2(new Vec2(10, 10), new Vec2(10, 10)).eq(new Rect2(new Vec2(10, 10), new Vec2(10, 10)))).toBeTruthy();
expect(new Rect2(new Vec2(10, 10), new Vec2(10, 10)).eq(new Rect2(new Vec2(20, 20), new Vec2(20, 20)))).toBeFalsy();
});
it('should make valid not equal comparisons', () => {
expect(new Rect2(new Vec2(10, 10), new Vec2(10, 10)).ne(new Rect2(new Vec2(10, 10), new Vec2(10, 10)))).toBeFalsy();
expect(new Rect2(new Vec2(10, 10), new Vec2(10, 10)).ne(new Rect2(new Vec2(20, 20), new Vec2(20, 20)))).toBeTruthy();
});
});

191
src/Squidex/app/framework/utils/rect2.ts

@ -1,191 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Rotation } from './rotation';
import { Vec2 } from './vec2';
export class Rect2 {
public static readonly ZERO = new Rect2(Vec2.ZERO, Vec2.ZERO);
public static readonly EMPTY = new Rect2(Vec2.NEGATIVE_INFINITE, Vec2.NEGATIVE_INFINITE);
public static readonly INFINITE = new Rect2(Vec2.POSITIVE_INFINITE, Vec2.POSITIVE_INFINITE);
public get center(): Vec2 {
return new Vec2(this.position.x + (0.5 * this.size.x), this.position.y + (0.5 * this.size.y));
}
public get area(): number {
return this.size.x * this.size.y;
}
public get x(): number {
return this.position.x;
}
public get y(): number {
return this.position.y;
}
public get left(): number {
return this.position.x;
}
public get top(): number {
return this.position.y;
}
public get right(): number {
return this.position.x + this.size.x;
}
public get bottom(): number {
return this.position.y + this.size.y;
}
public get width(): number {
return this.size.x;
}
public get height(): number {
return this.size.y;
}
public get centerX(): number {
return this.position.x + (0.5 * this.size.x);
}
public get centerY(): number {
return this.position.y + (0.5 * this.size.y);
}
public get isEmpty(): boolean {
return this.size.x < 0 || this.size.y < 0;
}
public get isInfinite(): boolean {
return this.size.x === Number.POSITIVE_INFINITY || this.size.y === Number.POSITIVE_INFINITY;
}
constructor(
public readonly position: Vec2,
public readonly size: Vec2
) {
}
public static createFromVecs(vecs: Vec2[] | null): Rect2 {
if (!vecs || vecs.length === 0) {
return Rect2.EMPTY;
}
let minX = Number.MAX_VALUE;
let minY = Number.MAX_VALUE;
let maxX = Number.MIN_VALUE;
let maxY = Number.MIN_VALUE;
for (let v of vecs) {
minX = Math.min(minX, v.x);
minY = Math.min(minY, v.y);
maxX = Math.max(maxX, v.x);
maxY = Math.max(maxY, v.y);
}
return new Rect2(new Vec2(minX, minY), new Vec2(Math.max(0, maxX - minX), Math.max(0, maxY - minY)));
}
public static createFromRects(rects: Rect2[] | null): Rect2 {
if (!rects || rects.length === 0) {
return Rect2.EMPTY;
}
let minX = Number.MAX_VALUE;
let minY = Number.MAX_VALUE;
let maxX = Number.MIN_VALUE;
let maxY = Number.MIN_VALUE;
for (let r of rects) {
minX = Math.min(minX, r.left);
minY = Math.min(minY, r.top);
maxX = Math.max(maxX, r.right);
maxY = Math.max(maxY, r.bottom);
}
return new Rect2(new Vec2(minX, minY), new Vec2(Math.max(0, maxX - minX), Math.max(0, maxY - minY)));
}
public static createRotated(position: Vec2, size: Vec2, rotation: Rotation): Rect2 {
const x = position.x;
const y = position.y;
const w = size.x;
const h = size.y;
if (Math.abs(rotation.sin) < Number.EPSILON) {
return new Rect2(position, size);
}
const center = new Vec2(x + (w * 0.5), y + (h * 0.5));
const lt = Vec2.createRotated(new Vec2(x + 0, y + 0), center, rotation);
const rt = Vec2.createRotated(new Vec2(x + w, y + 0), center, rotation);
const rb = Vec2.createRotated(new Vec2(x + w, y + h), center, rotation);
const lb = Vec2.createRotated(new Vec2(x + 0, y + h), center, rotation);
return Rect2.createFromVecs([lb, lt, rb, rt]);
}
public eq(r: Rect2): boolean {
return this.position.eq(r.position) && this.size.eq(r.size);
}
public ne(r: Rect2): boolean {
return this.position.ne(r.position) || this.size.ne(r.size);
}
public toString(): string {
return `(x: ${this.x}, y: ${this.y}, w: ${this.width}, h: ${this.height})`;
}
public inflateV(v: Vec2): Rect2 {
return this.inflate(v.x, v.y);
}
public inflate(w: number, h: number): Rect2 {
return new Rect2(new Vec2(this.position.x - w, this.position.y - h), new Vec2(this.size.x + (2 * w), this.size.y + (2 * h)));
}
public deflateV(v: Vec2): Rect2 {
return this.deflate(v.x, v.y);
}
public deflate(w: number, h: number): Rect2 {
return new Rect2(new Vec2(this.position.x + w, this.position.y + h), new Vec2(Math.max(0, this.size.x - (2 * w)), Math.max(0, this.size.y - (2 * h))));
}
public containsRect(r: Rect2): boolean {
return r.left >= this.left && r.right <= this.right && r.top >= this.top && r.bottom <= this.bottom;
}
public containsVec(v: Vec2): boolean {
return v.x >= this.position.x && v.x - this.size.x <= this.position.x && v.y >= this.position.y && v.y - this.size.y <= this.position.y;
}
public intersectsWith(r: Rect2): boolean {
return !this.isEmpty && !r.isEmpty && ((r.left <= this.right && r.right >= this.left && r.top <= this.bottom && r.bottom >= this.top) || this.isInfinite || r.isInfinite);
}
public intersect(r: Rect2): Rect2 {
if (!this.intersectsWith(r)) {
return Rect2.EMPTY;
}
const minX = Math.max(this.x, r.x);
const minY = Math.max(this.y, r.y);
const w = Math.min(this.position.x + this.size.x, r.position.x + r.size.x) - minX;
const h = Math.min(this.position.y + this.size.y, r.position.y + r.size.y) - minY;
return new Rect2(new Vec2(minX, minY), new Vec2(Math.max(w, 0.0), Math.max(h, 0.0)));
}
}

73
src/Squidex/app/framework/utils/rotation.spec.ts

@ -1,73 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Rotation } from './../';
describe('Rotation', () => {
it('should sub correctly', () => {
const rotation1 = Rotation.createFromDegree(180);
const rotation2 = Rotation.createFromDegree(45);
const actual = rotation1.sub(rotation2);
const expected = Rotation.createFromDegree(135);
expect(actual).toEqual(expected);
});
it('should add correctly', () => {
const rotation1 = Rotation.createFromDegree(180);
const rotation2 = Rotation.createFromDegree(45);
const actual = rotation1.add(rotation2);
const expected = Rotation.createFromDegree(225);
expect(actual).toEqual(expected);
});
it('should calculate negated rotation', () => {
const rotation = Rotation.createFromDegree(180);
const actual = rotation.negate();
const expected = Rotation.createFromDegree(-180);
expect(actual).toEqual(expected);
});
it('should create rotation by degree', () => {
const rotation = Rotation.createFromDegree(180);
expect(rotation.degree).toBe(180);
expect(rotation.radian).toBe(Math.PI);
expect(rotation.cos).toBe(Math.cos(Math.PI));
expect(rotation.sin).toBe(Math.sin(Math.PI));
expect(rotation.toString()).toBe('180°');
});
it('should create rotation by radian', () => {
const rotation = Rotation.createFromRadian(Math.PI);
expect(rotation.degree).toBe(180);
expect(rotation.radian).toBe(Math.PI);
expect(rotation.cos).toBe(Math.cos(Math.PI));
expect(rotation.sin).toBe(Math.sin(Math.PI));
expect(rotation.toString()).toBe('180°');
});
it('should make correct equal comparisons', () => {
expect(Rotation.createFromDegree(123).eq(Rotation.createFromDegree(123))).toBeTruthy();
expect(Rotation.createFromDegree(123).eq(Rotation.createFromDegree(234))).toBeFalsy();
});
it('should make correct not equal comparisons', () => {
expect(Rotation.createFromDegree(123).ne(Rotation.createFromDegree(123))).toBeFalsy();
expect(Rotation.createFromDegree(123).ne(Rotation.createFromDegree(234))).toBeTruthy();
});
});

57
src/Squidex/app/framework/utils/rotation.ts

@ -1,57 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { MathHelper } from './math-helper';
export class Rotation {
public static readonly ZERO = Rotation.createFromRadian(0);
public cos: number;
public sin: number;
constructor(
public readonly radian: number,
public readonly degree: number
) {
this.cos = Math.cos(radian);
this.sin = Math.sin(radian);
Object.freeze(this);
}
public static createFromRadian(radian: number): Rotation {
return new Rotation(radian, MathHelper.toDegree(radian));
}
public static createFromDegree(degree: number): Rotation {
return new Rotation(MathHelper.toRad(degree), degree);
}
public eq(r: Rotation): boolean {
return this.radian === r.radian;
}
public ne(r: Rotation): boolean {
return this.radian !== r.radian;
}
public toString(): string {
return `${this.degree}°`;
}
public add(r: Rotation): Rotation {
return Rotation.createFromDegree(MathHelper.toPositiveDegree(this.degree + r.degree));
}
public sub(r: Rotation): Rotation {
return Rotation.createFromDegree(MathHelper.toPositiveDegree(this.degree - r.degree));
}
public negate(): Rotation {
return Rotation.createFromDegree(-this.degree);
}
}

166
src/Squidex/app/framework/utils/vec2.spec.ts

@ -1,166 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Rotation, Vec2 } from './../';
describe('Vec2', () => {
it('should instantiate from x and y value', () => {
const v = new Vec2(10, 20);
expect(v.x).toBe(10);
expect(v.y).toBe(20);
expect(v.toString()).toBe('(10, 20)');
});
it('should make valid equal comparisons', () => {
expect(new Vec2(10, 10).eq(new Vec2(10, 10))).toBeTruthy();
expect(new Vec2(10, 10).eq(new Vec2(20, 20))).toBeFalsy();
});
it('should make valid not equal comparisons', () => {
expect(new Vec2(10, 10).ne(new Vec2(20, 20))).toBeTruthy();
expect(new Vec2(10, 10).ne(new Vec2(10, 10))).toBeFalsy();
});
it('should round with default value', () => {
const actual = new Vec2(1.3, 1.6).round();
const expected = new Vec2(1, 2);
expect(actual).toEqual(expected);
});
it('should calculate multiple of 10', () => {
const actual = new Vec2(13, 16).round(10);
const expected = new Vec2(10, 20);
expect(actual).toEqual(expected);
});
it('should calculate multiple of 2', () => {
const actual = new Vec2(13, 12.2).roundToMultipleOfTwo();
const expected = new Vec2(14, 12);
expect(actual).toEqual(expected);
});
it('should add by vector correctly', () => {
const actual = new Vec2(15, 12).add(new Vec2(4, 1));
const expected = new Vec2(19, 13);
expect(actual).toEqual(expected);
});
it('should add by scalar correctly', () => {
const actual = new Vec2(15, 12).addScalar(3);
const expected = new Vec2(18, 15);
expect(actual).toEqual(expected);
});
it('should subtract by vector correctly', () => {
const actual = new Vec2(15, 12).sub(new Vec2(4, 1));
const expected = new Vec2(11, 11);
expect(actual).toEqual(expected);
});
it('should subtract by scalar correctly', () => {
const actual = new Vec2(15, 12).subScalar(3);
const expected = new Vec2(12, 9);
expect(actual).toEqual(expected);
});
it('should multiply by vector correctly', () => {
const actual = new Vec2(15, 12).mul(new Vec2(4, 2));
const expected = new Vec2(60, 24);
expect(actual).toEqual(expected);
});
it('should multiply by scalar correctly', () => {
const actual = new Vec2(15, 12).mulScalar(3);
const expected = new Vec2(45, 36);
expect(actual).toEqual(expected);
});
it('should divide by vector correctly', () => {
const actual = new Vec2(15, 12).div(new Vec2(5, 2));
const expected = new Vec2(3, 6);
expect(actual).toEqual(expected);
});
it('should divide by scalar correctly', () => {
const actual = new Vec2(15, 12).divScalar(3);
const expected = new Vec2(5, 4);
expect(actual).toEqual(expected);
});
it('should negate correctly', () => {
const actual = new Vec2(15, 12).negate();
const expected = new Vec2(-15, -12);
expect(actual).toEqual(expected);
});
it('should calculate max vector', () => {
const actual = Vec2.max(new Vec2(20, 10), new Vec2(15, 30));
const expected = new Vec2(20, 30);
expect(actual).toEqual(expected);
});
it('should calculate min vector', () => {
const actual = Vec2.min(new Vec2(20, 10), new Vec2(15, 30));
const expected = new Vec2(15, 10);
expect(actual).toEqual(expected);
});
it('should calculate length', () => {
const actual = new Vec2(10, 10).length;
const expected = Math.sqrt(200);
expect(actual).toBe(expected);
});
it('should calculate length squared', () => {
const actual = new Vec2(10, 10).lengtSquared;
const expected = 200;
expect(actual).toBe(expected);
});
it('should calculate median', () => {
const actual = Vec2.createMedian(new Vec2(10, 20), new Vec2(20, 20), new Vec2(60, 20));
const expected = new Vec2(30, 20);
expect(actual).toEqual(expected);
});
it('should calculate rotated vector correctly', () => {
const source = new Vec2(40, 20);
const center = new Vec2(20, 20);
const rotation = Rotation.createFromRadian(Math.PI / 2);
const actual = Vec2.createRotated(source, center, rotation);
const expected = new Vec2(20, 40);
expect(actual).toEqual(expected);
});
it('should calculate angles between vectors', () => {
expect(Vec2.angleBetween(new Vec2(1, 0), new Vec2(1, 0))).toBe(0);
expect(Vec2.angleBetween(new Vec2(1, 0), new Vec2(1, 1))).toBe(45);
expect(Vec2.angleBetween(new Vec2(1, 0), new Vec2(0, 1))).toBe(90);
});
});

127
src/Squidex/app/framework/utils/vec2.ts

@ -1,127 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { MathHelper } from './math-helper';
import { Rotation } from './rotation';
export class Vec2 {
public static readonly ZERO = new Vec2(0, 0);
public static readonly ONE = new Vec2(1, 1);
public static readonly POSITIVE_INFINITE = new Vec2(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY);
public static readonly NEGATIVE_INFINITE = new Vec2(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY);
public get length(): number {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
public get lengtSquared(): number {
return this.x * this.x + this.y * this.y;
}
constructor(
public readonly x: number,
public readonly y: number
) {
Object.freeze(this);
}
public eq(v: Vec2): boolean {
return this.x === v.x && this.y === v.y;
}
public ne(v: Vec2): boolean {
return this.x !== v.x || this.y !== v.y;
}
public toString(): string {
return `(${this.x}, ${this.y})`;
}
public add(v: Vec2): Vec2 {
return new Vec2(this.x + v.x, this.y + v.y);
}
public addScalar(s: number): Vec2 {
return new Vec2(this.x + s, this.y + s);
}
public sub(v: Vec2): Vec2 {
return new Vec2(this.x - v.x, this.y - v.y);
}
public subScalar(s: number): Vec2 {
return new Vec2(this.x - s, this.y - s);
}
public mul(v: Vec2): Vec2 {
return new Vec2(this.x * v.x, this.y * v.y);
}
public mulScalar(s: number): Vec2 {
return new Vec2(this.x * s, this.y * s);
}
public div(v: Vec2): Vec2 {
return new Vec2(this.x / v.x, this.y / v.y);
}
public divScalar(s: number): Vec2 {
return new Vec2(this.x / s, this.y / s);
}
public negate(): Vec2 {
return new Vec2(-this.x, -this.y);
}
public static max(lhs: Vec2, rhs: Vec2): Vec2 {
return new Vec2(Math.max(lhs.x, rhs.x), Math.max(lhs.y, rhs.y));
}
public static min(lhs: Vec2, rhs: Vec2): Vec2 {
return new Vec2(Math.min(lhs.x, rhs.x), Math.min(lhs.y, rhs.y));
}
public round(factor = 1): Vec2 {
return new Vec2(MathHelper.roundToMultipleOf(this.x, factor), MathHelper.roundToMultipleOf(this.y, factor));
}
public roundToMultipleOfTwo(): Vec2 {
return new Vec2(MathHelper.roundToMultipleOf(this.x, 2), MathHelper.roundToMultipleOf(this.y, 2));
}
public static createRotated(vec: Vec2, center: Vec2, rotation: Rotation): Vec2 {
const x = vec.x - center.x;
const y = vec.y - center.y;
const result = new Vec2(
(x * rotation.cos) - (y * rotation.sin) + center.x,
(x * rotation.sin) + (y * rotation.cos) + center.y);
return result;
}
public static createMedian(...vecs: Vec2[]) {
let medianX = 0;
let medianY = 0;
for (let v of vecs) {
medianX += v.x;
medianY += v.y;
}
return new Vec2(medianX / vecs.length, medianY / vecs.length);
}
public static angleBetween(lhs: Vec2, rhs: Vec2): number {
const y = (lhs.x * rhs.y) - (rhs.x * lhs.y);
const x = (lhs.x * rhs.x) + (lhs.y * rhs.y);
return MathHelper.toPositiveDegree(MathHelper.toDegree(Math.atan2(y, x)));
}
}

3
src/Squidex/app/shared/components/dashboard-link.directive.ts

@ -7,6 +7,7 @@
import { Directive, ElementRef, HostListener, OnDestroy, OnInit, Renderer } from '@angular/core';
import { Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { AppsStoreService } from './../services/apps-store.service';
@ -14,7 +15,7 @@ import { AppsStoreService } from './../services/apps-store.service';
selector: '[dashboardLink]'
})
export class DashboardLinkDirective implements OnInit, OnDestroy {
private appSubscription: any;
private appSubscription: Subscription;
private url: string;
constructor(

2
src/Squidex/app/shared/services/auth.service.ts

@ -187,7 +187,7 @@ export class AuthService {
private checkResponse(response: Observable<Response>) {
return response.catch((error: Response) => {
if (error.status === 401 || error.status === 404) {
if (error.status === 401) {
this.logoutRedirect();
return Observable.empty<Response>();

17
src/Squidex/app/shared/services/schemas.service.ts

@ -33,6 +33,12 @@ export function createProperties(fieldType: string, values: {} | null = null): F
undefined, undefined, undefined, false,
undefined, undefined, undefined, undefined, undefined, undefined, undefined);
break;
case 'boolean':
properties =
new BooleanFieldPropertiesDto(
undefined, undefined, undefined, false,
undefined, undefined);
break;
default:
throw 'Invalid properties type';
}
@ -120,6 +126,17 @@ export class StringFieldPropertiesDto extends FieldPropertiesDto {
}
}
export class BooleanFieldPropertiesDto extends FieldPropertiesDto {
constructor(label: string, hints: string, placeholder: string, isRequired: boolean,
public readonly editor: string,
public readonly defaultValue: boolean | null
) {
super(label, hints, placeholder, isRequired);
this['fieldType'] = 'boolean';
}
}
export class UpdateSchemaDto {
constructor(
public readonly label: string,

18
src/Squidex/app/shell/pages/app/left-menu.component.ts

@ -6,8 +6,9 @@
*/
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { AppsStoreService, LocalStoreService } from 'shared';
import { AppsStoreService } from 'shared';
@Component({
selector: 'sqx-left-menu',
@ -15,20 +16,11 @@ import { AppsStoreService, LocalStoreService } from 'shared';
templateUrl: './left-menu.component.html'
})
export class LeftMenuComponent implements OnInit, OnDestroy {
private appSubscription: any | null = null;
public get showSettingsMenu(): boolean {
return this.localStore.get('squidex:showSettingsMenu') === 'true';
}
public set showSettingsMenu(value: boolean) {
this.localStore.set('squidex:showSettingsMenu', value);
}
private appSubscription: Subscription;
public permission: string | null = null;
constructor(
private readonly localStore: LocalStoreService,
private readonly appsStore: AppsStoreService
) {
}
@ -45,8 +37,4 @@ export class LeftMenuComponent implements OnInit, OnDestroy {
public ngOnDestroy() {
this.appSubscription.unsubscribe();
}
public toggleSettingsMenu() {
this.showSettingsMenu = !this.showSettingsMenu;
}
}

13
src/Squidex/app/shell/pages/home/home-page.component.ts

@ -5,37 +5,30 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService, TitleService } from 'shared';
import { AuthService } from 'shared';
@Component({
selector: 'home-page',
styleUrls: ['./home-page.component.scss'],
templateUrl: './home-page.component.html'
})
export class HomePageComponent implements OnInit {
export class HomePageComponent {
public showLoginError = false;
constructor(
private readonly auth: AuthService,
private readonly title: TitleService,
private readonly router: Router
) {
}
public ngOnInit() {
this.title.setTitle('Home');
}
public login() {
this.auth.loginPopup()
.subscribe(() => {
this.router.navigate(['/app']);
}, ex => {
this.title.setTitle('Login failed');
this.showLoginError = true;
});
}

5
src/Squidex/app/shell/pages/internal/apps-menu.component.ts

@ -6,6 +6,7 @@
*/
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import {
AppDto,
@ -25,8 +26,8 @@ const FALLBACK_NAME = 'Apps Overview';
]
})
export class AppsMenuComponent implements OnInit, OnDestroy {
private appsSubscription: any | null = null;
private appSubscription: any | null = null;
private appsSubscription: Subscription;
private appSubscription: Subscription;
public modalMenu = new ModalView(false, true);
public modalDialog = new ModalView();

3
src/Squidex/app/shell/pages/internal/internal-area.component.ts

@ -6,6 +6,7 @@
*/
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import {
Notification,
@ -18,7 +19,7 @@ import {
templateUrl: './internal-area.component.html'
})
export class InternalAreaComponent implements OnInit, OnDestroy {
private notificationsSubscription: any;
private notificationsSubscription: Subscription;
public notifications: Notification[] = [];

3
src/Squidex/app/shell/pages/internal/profile-menu.component.ts

@ -6,6 +6,7 @@
*/
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import {
AuthService,
@ -22,7 +23,7 @@ import {
]
})
export class ProfileMenuComponent implements OnInit, OnDestroy {
private authenticationSubscription: any | null = null;
private authenticationSubscription: Subscription;
public modalMenu = new ModalView(false, true);

2
src/Squidex/app/shell/pages/not-found/not-found-page.component.html

@ -1,3 +1,5 @@
<sqx-title message="Not Found"></sqx-title>
<div class="content">
<img class="logo" src="/images/logo.png" />

15
src/Squidex/app/shell/pages/not-found/not-found-page.component.ts

@ -5,22 +5,11 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, OnInit } from '@angular/core';
import { TitleService } from 'shared';
import { Component } from '@angular/core';
@Component({
selector: 'sqx-not-found-page',
styleUrls: ['./not-found-page.component.scss'],
templateUrl: './not-found-page.component.html'
})
export class NotFoundPageComponent implements OnInit {
constructor(
private readonly title: TitleService
) {
}
public ngOnInit() {
this.title.setTitle('Not found');
}
}
export class NotFoundPageComponent { }

11
src/Squidex/app/theme/_bootstrap.scss

@ -149,11 +149,10 @@
text-align: right;
}
select {
&.form-control {
padding-top: 0;
padding-bottom: 0;
}
.form-control {
padding-top: 0;
padding-bottom: 0;
height: calc(2.5rem - 2px);
}
.form-hint {
@ -199,8 +198,6 @@ select {
}
}
.modal {
&-content,
&-header {

2
src/Squidex/wwwroot/index.html

@ -38,7 +38,7 @@
<div class="loading">
<img src="/images/loader.gif" />
<div>Loading awesomeness</div>
<div>Loading Squidex</div>
</div>
</sqx-app>
</body>

Loading…
Cancel
Save