Initial commit
This commit is contained in:
14
src/infrastructure/Clipboard.ts
Normal file
14
src/infrastructure/Clipboard.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
export class Clipboard {
|
||||
public static copyText(text: string): void {
|
||||
const el = document.createElement('textarea');
|
||||
el.value = text;
|
||||
el.setAttribute('readonly', ''); // to avoid focus
|
||||
el.style.position = 'absolute';
|
||||
el.style.left = '-9999px';
|
||||
document.body.appendChild(el);
|
||||
el.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(el);
|
||||
}
|
||||
}
|
||||
14
src/infrastructure/Entity/BaseEntity.ts
Normal file
14
src/infrastructure/Entity/BaseEntity.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { IEntity } from './IEntity';
|
||||
|
||||
export abstract class BaseEntity<TId> implements IEntity<TId> {
|
||||
constructor(public id: TId) {
|
||||
if (typeof id !== 'number' && !id) {
|
||||
throw new Error('Id cannot be null or empty');
|
||||
}
|
||||
}
|
||||
public equals(otherId: TId): boolean {
|
||||
return this.id === otherId;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5
src/infrastructure/Entity/IEntity.ts
Normal file
5
src/infrastructure/Entity/IEntity.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/** Aggregate root */
|
||||
export interface IEntity<TId> {
|
||||
id: TId;
|
||||
equals(other: TId): boolean;
|
||||
}
|
||||
4
src/infrastructure/Events/ISignal.ts
Normal file
4
src/infrastructure/Events/ISignal.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface ISignal<T> {
|
||||
on(handler: (data: T) => void): void;
|
||||
off(handler: (data: T) => void): void;
|
||||
}
|
||||
18
src/infrastructure/Events/Signal.ts
Normal file
18
src/infrastructure/Events/Signal.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { ISignal } from './ISignal';
|
||||
export { ISignal };
|
||||
|
||||
export class Signal<T> implements ISignal<T> {
|
||||
private handlers: Array<(data: T) => void> = [];
|
||||
|
||||
public on(handler: (data: T) => void): void {
|
||||
this.handlers.push(handler);
|
||||
}
|
||||
|
||||
public off(handler: (data: T) => void): void {
|
||||
this.handlers = this.handlers.filter((h) => h !== handler);
|
||||
}
|
||||
|
||||
public notify(data: T) {
|
||||
this.handlers.slice(0).forEach((h) => h(data));
|
||||
}
|
||||
}
|
||||
9
src/infrastructure/Repository/IRepository.ts
Normal file
9
src/infrastructure/Repository/IRepository.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { IEntity } from '../Entity/IEntity';
|
||||
|
||||
export interface IRepository<TKey, TEntity extends IEntity<TKey>> {
|
||||
readonly length: number;
|
||||
getItems(predicate?: (entity: TEntity) => boolean): TEntity[];
|
||||
addItem(item: TEntity): void;
|
||||
removeItem(id: TKey): void;
|
||||
exists(item: TEntity): boolean;
|
||||
}
|
||||
40
src/infrastructure/Repository/InMemoryRepository.ts
Normal file
40
src/infrastructure/Repository/InMemoryRepository.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { IEntity } from '../Entity/IEntity';
|
||||
import { IRepository } from './IRepository';
|
||||
|
||||
export class InMemoryRepository<TKey, TEntity extends IEntity<TKey>> implements IRepository<TKey, TEntity> {
|
||||
private readonly items: TEntity[];
|
||||
constructor(items?: TEntity[]) {
|
||||
this.items = items || new Array<TEntity>();
|
||||
}
|
||||
|
||||
public get length(): number {
|
||||
return this.items.length;
|
||||
}
|
||||
|
||||
public getItems(predicate?: (entity: TEntity) => boolean): TEntity[] {
|
||||
return predicate ? this.items.filter(predicate) : this.items;
|
||||
}
|
||||
|
||||
public addItem(item: TEntity): void {
|
||||
if (!item) {
|
||||
throw new Error('Item is null');
|
||||
}
|
||||
if (this.exists(item)) {
|
||||
throw new Error(`Cannot add (id: ${item.id}) as it is already exists`);
|
||||
}
|
||||
this.items.push(item);
|
||||
}
|
||||
|
||||
public removeItem(id: TKey): void {
|
||||
const index = this.items.findIndex((item) => item.id === id);
|
||||
if (index === -1) {
|
||||
throw new Error(`Cannot remove (id: ${id}) as it does not exist`);
|
||||
}
|
||||
this.items.splice(index, 1);
|
||||
}
|
||||
|
||||
public exists(entity: TEntity): boolean {
|
||||
const index = this.items.findIndex((item) => item.id === entity.id);
|
||||
return index !== -1;
|
||||
}
|
||||
}
|
||||
16
src/infrastructure/SaveFileDialog.ts
Normal file
16
src/infrastructure/SaveFileDialog.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import fileSaver from 'file-saver';
|
||||
|
||||
export class SaveFileDialog {
|
||||
public static saveText(text: string, fileName: string): void {
|
||||
this.saveBlob(text, 'text/plain;charset=utf-8', fileName);
|
||||
}
|
||||
|
||||
private static saveBlob(file: BlobPart, fileType: string, fileName: string): void {
|
||||
try {
|
||||
const blob = new Blob([file], { type: fileType });
|
||||
fileSaver.saveAs(blob, fileName);
|
||||
} catch (e) {
|
||||
window.open('data:' + fileType + ',' + encodeURIComponent(file.toString()), '_blank', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/infrastructure/Threading/AsyncLazy.ts
Normal file
34
src/infrastructure/Threading/AsyncLazy.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Signal } from '../Events/Signal';
|
||||
|
||||
export class AsyncLazy<T> {
|
||||
private valueCreated = new Signal();
|
||||
private isValueCreated = false;
|
||||
private isCreatingValue = false;
|
||||
private value: T | undefined;
|
||||
|
||||
constructor(private valueFactory: () => Promise<T>) { }
|
||||
|
||||
public setValueFactory(valueFactory: () => Promise<T>) {
|
||||
this.valueFactory = valueFactory;
|
||||
}
|
||||
|
||||
public async getValueAsync(): Promise<T> {
|
||||
// If value is already created, return the value directly
|
||||
if (this.isValueCreated) {
|
||||
return Promise.resolve(this.value as T);
|
||||
}
|
||||
// If value is being created, wait until the value is created and then return it.
|
||||
if (this.isCreatingValue) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
// Return/result when valueCreated event is triggered.
|
||||
this.valueCreated.on(() => resolve(this.value));
|
||||
});
|
||||
}
|
||||
this.isCreatingValue = true;
|
||||
this.value = await this.valueFactory();
|
||||
this.isCreatingValue = false;
|
||||
this.isValueCreated = true;
|
||||
this.valueCreated.notify(null);
|
||||
return Promise.resolve(this.value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user