From f0f89078260474e21612392f13ecfad3155e25f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20=C5=9Eahin=20=C3=96z=C3=A7elik?= Date: Sat, 16 May 2020 22:16:09 +0300 Subject: [PATCH] feat: created image-input --- .github/workflows/publish.yml | 29 ++++ README.md | 24 +--- angular.json | 12 +- package-lock.json | 5 + package.json | 14 +- .../image-input/image-input.directive.spec.ts | 8 ++ .../form/image-input/image-input.directive.ts | 133 ++++++++++++++++++ .../form/image-input/image-input.module.ts | 18 +++ .../src/lib/form/image-input/index.ts | 2 + projects/ng-utils/src/public-api.ts | 8 +- projects/ng-utils/src/utils/share-last.ts | 6 + projects/ng-utils/src/utils/unsubscriber.ts | 21 +++ .../playground/src/app/app.component.html | 23 +-- .../playground/src/app/app.component.scss | 4 + projects/playground/src/app/app.component.ts | 5 +- projects/playground/src/app/app.module.ts | 6 +- projects/playground/src/assets/angular.svg | 5 + tsconfig.json | 10 +- 18 files changed, 275 insertions(+), 58 deletions(-) create mode 100644 .github/workflows/publish.yml create mode 100644 projects/ng-utils/src/lib/form/image-input/image-input.directive.spec.ts create mode 100644 projects/ng-utils/src/lib/form/image-input/image-input.directive.ts create mode 100644 projects/ng-utils/src/lib/form/image-input/image-input.module.ts create mode 100644 projects/ng-utils/src/lib/form/image-input/index.ts create mode 100644 projects/ng-utils/src/utils/share-last.ts create mode 100644 projects/ng-utils/src/utils/unsubscriber.ts create mode 100644 projects/playground/src/assets/angular.svg diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..e919222 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,29 @@ +name: Npm Package + +on: + release: + types: [created] + +jobs: + publish-npm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set env + run: echo ::set-env name=PACKAGE_VERSION::$(echo ${GITHUB_REF:10}) + - uses: actions/setup-node@v1 + with: + node-version: 12 + registry-url: https://registry.npmjs.org/ + - run: npm install + # - run: npm test + - run: npm run version-update + - run: npm run build + # - run: npm run change-ts-version + # - run: rm -rf node_modules + # - run: rm ./package-lock.json + # - run: npm install + # - run: npm run prepare:legacy + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} diff --git a/README.md b/README.md index cc26671..a8e38ed 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,5 @@ # NgUtils -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.0.6. +A package to store commonly used angular application contents -## Development server -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. - -## Code scaffolding - -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. - -## Build - -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. - -## Running unit tests - -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). - -## Running end-to-end tests - -Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). - -## Further help - -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/angular.json b/angular.json index aec0045..040f5c5 100644 --- a/angular.json +++ b/angular.json @@ -7,7 +7,12 @@ "projectType": "library", "root": "projects/ng-utils", "sourceRoot": "projects/ng-utils/src", - "prefix": "lib", + "prefix": "tha", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, "architect": { "build": { "builder": "@angular-devkit/build-ng-packagr:build", @@ -97,7 +102,8 @@ "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { - "browserTarget": "playground:build" + "browserTarget": "playground:build", + "port": 3600 }, "configurations": { "production": { @@ -156,4 +162,4 @@ } }}, "defaultProject": "ng-utils" -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index fffa30b..3ce88da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -482,6 +482,11 @@ "defer-to-connect": "^1.0.1" } }, + "@thalesrc/js-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@thalesrc/js-utils/-/js-utils-2.0.8.tgz", + "integrity": "sha512-gd9PTd6mP7fSrcd7jPSzjhyTbXHRsANghiIwGaL8ygvdt5en5k7t0B8r8jhRLyd70sZvoBNKu9/TW76nT5B0eA==" + }, "@types/estree": { "version": "0.0.44", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.44.tgz", diff --git a/package.json b/package.json index 31751ba..a1fc854 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,8 @@ { - "name": "ng-utils", + "name": "@thalesrc/ng-utils", "version": "0.0.0", + "description": "A package to store commonly used angular application contents", + "keywords": ["angular", "ng", "util", "utils", "library", "image-input"], "author": { "name": "Ali Şahin Özçelik", "email": "alisahinozcelik@gmail.com" @@ -8,11 +10,16 @@ "scripts": { "start": "ng serve playground", "build": "ng build ng-utils --prod", + "version-update": "npm version $PACKAGE_VERSION --no-git-tag-version", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" }, + "license": "MIT", "private": false, + "publishConfig": { + "access": "public" + }, "dependencies": { "@angular/animations": "~8.0.3", "@angular/common": "~8.0.3", @@ -22,19 +29,20 @@ "@angular/platform-browser": "~8.0.3", "@angular/platform-browser-dynamic": "~8.0.3", "@angular/router": "~8.0.3", + "@thalesrc/js-utils": "^2.0.8", "rxjs": "~6.4.0", "tslib": "^1.9.0", "zone.js": "~0.9.1" }, "devDependencies": { - "@angular-devkit/build-angular": "~0.800.6", + "@angular-devkit/build-angular": "^0.800.6", "@angular-devkit/build-ng-packagr": "~0.800.6", "@angular/cli": "~8.0.6", "@angular/compiler-cli": "~8.0.3", "@angular/language-service": "~8.0.3", - "@types/node": "~8.9.4", "@types/jasmine": "~3.3.8", "@types/jasminewd2": "~2.0.3", + "@types/node": "~8.9.4", "codelyzer": "^5.0.0", "jasmine-core": "~3.4.0", "jasmine-spec-reporter": "~4.2.1", diff --git a/projects/ng-utils/src/lib/form/image-input/image-input.directive.spec.ts b/projects/ng-utils/src/lib/form/image-input/image-input.directive.spec.ts new file mode 100644 index 0000000..6e59c71 --- /dev/null +++ b/projects/ng-utils/src/lib/form/image-input/image-input.directive.spec.ts @@ -0,0 +1,8 @@ +import { ImageInputDirective } from './image-input.directive'; + +describe('ImageInputDirective', () => { + it('should create an instance', () => { + const directive = new ImageInputDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/projects/ng-utils/src/lib/form/image-input/image-input.directive.ts b/projects/ng-utils/src/lib/form/image-input/image-input.directive.ts new file mode 100644 index 0000000..cdb0089 --- /dev/null +++ b/projects/ng-utils/src/lib/form/image-input/image-input.directive.ts @@ -0,0 +1,133 @@ +import { Directive, forwardRef, Input, OnInit, ElementRef, HostListener } from '@angular/core'; +import { NG_VALIDATORS, NG_VALUE_ACCESSOR, ControlValueAccessor, Validator } from '@angular/forms'; +import { noop } from '@thalesrc/js-utils/function/noop'; +import { BehaviorSubject, combineLatest, fromEvent, merge } from 'rxjs'; +import { map, distinctUntilChanged, first } from 'rxjs/operators'; +import { Unsubscriber } from 'projects/ng-utils/src/utils/unsubscriber'; +import { shareLast } from 'projects/ng-utils/src/utils/share-last'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: 'img[thaImageInput]', + providers: [ + { provide: NG_VALIDATORS, useExisting: ImageInputDirective, multi: true }, + { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ImageInputDirective), multi: true} + ], + exportAs: 'thaImageInput' +}) +export class ImageInputDirective extends Unsubscriber implements ControlValueAccessor, Validator, OnInit { + // tslint:disable-next-line:max-line-length + private static TRANSPARENT_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8Wg8AAm8BdpLdi+IAAAAASUVORK5CYII='; + + private onChange: (value: File) => void = noop; + private onTouched: () => void = noop; + private onValidatorChange: () => void = noop; + + private input = ImageInputDirective.createInput(); + + private src$ = new BehaviorSubject(ImageInputDirective.TRANSPARENT_URL); + private modelFile$ = new BehaviorSubject(null); + + private emptySource$ = this.src$.pipe( + distinctUntilChanged() + ); + + private inputChange$ = fromEvent(this.input, 'change').pipe( + map(event => { + let file = this.input.files[0] || null; + + if (file && !['image/jpeg', 'image/png', 'image/gif'].some(type => type === file.type)) { + this.input.value = ''; + file = null; + } + + return file; + }) + ); + + public file$ = merge(this.modelFile$, this.inputChange$).pipe(shareLast()); + + @Input() + private disabled = false; + + @Input('src') + private set _src(src: string) { + console.log(src); + + this.src$.next(src); + } + + private static createInput() { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.jpg,.jpeg,.png,.gif'; + + return input; + } + + constructor( + private el: ElementRef + ) { + super(); + } + + @HostListener('click') + public onHostClick() { + if (this.disabled) { + return; + } + + this.onTouched(); + this.input.click(); + } + + public ngOnInit() { + this.subs = this.inputChange$.subscribe(value => { + this.onChange(value); + }); + + this.subs = combineLatest(this.file$, this.emptySource$).subscribe(([file, src]) => { + if (!file) { + this.el.nativeElement.src = src; + + return; + } + + this.el.nativeElement.src = URL.createObjectURL(file); + }); + } + + public registerOnChange(fn: (value: File) => void): void { + this.onChange = fn; + } + + public registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + public async writeValue(value: File) { + if (!value) { + value = null; + } + + const current = await this.file$.pipe(first()).toPromise(); + + if (value === current) { + return; + } + + this.modelFile$.next(value); + } + + public setDisabledState(disabled: boolean): void { + this.disabled = disabled; + } + + public registerOnValidatorChange(fn: () => void): void { + this.onValidatorChange = fn; + } + + public validate() { + return null; + } +} diff --git a/projects/ng-utils/src/lib/form/image-input/image-input.module.ts b/projects/ng-utils/src/lib/form/image-input/image-input.module.ts new file mode 100644 index 0000000..168bb6b --- /dev/null +++ b/projects/ng-utils/src/lib/form/image-input/image-input.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { CommonModule } from '@angular/common'; +import { ImageInputDirective } from './image-input.directive'; + +@NgModule({ + declarations: [ + ImageInputDirective + ], + imports: [ + CommonModule, + FormsModule, + ], + exports: [ + ImageInputDirective + ] +}) +export class ImageInputModule { } diff --git a/projects/ng-utils/src/lib/form/image-input/index.ts b/projects/ng-utils/src/lib/form/image-input/index.ts new file mode 100644 index 0000000..1009af1 --- /dev/null +++ b/projects/ng-utils/src/lib/form/image-input/index.ts @@ -0,0 +1,2 @@ +export * from './image-input.module'; +export * from './image-input.directive'; diff --git a/projects/ng-utils/src/public-api.ts b/projects/ng-utils/src/public-api.ts index 808ce41..fd31df2 100644 --- a/projects/ng-utils/src/public-api.ts +++ b/projects/ng-utils/src/public-api.ts @@ -2,6 +2,8 @@ * Public API Surface of ng-utils */ -export * from './lib/ng-utils.service'; -export * from './lib/ng-utils.component'; -export * from './lib/ng-utils.module'; +// export * from './lib/ng-utils.service'; +// export * from './lib/ng-utils.component'; +// export * from './lib/ng-utils.module'; +export * from './lib/form/image-input'; +export * from './utils/unsubscriber'; diff --git a/projects/ng-utils/src/utils/share-last.ts b/projects/ng-utils/src/utils/share-last.ts new file mode 100644 index 0000000..db9acda --- /dev/null +++ b/projects/ng-utils/src/utils/share-last.ts @@ -0,0 +1,6 @@ +import { MonoTypeOperatorFunction } from 'rxjs'; +import { shareReplay } from 'rxjs/operators'; + +export function shareLast(): MonoTypeOperatorFunction { + return shareReplay({refCount: false, bufferSize: 1}); +} diff --git a/projects/ng-utils/src/utils/unsubscriber.ts b/projects/ng-utils/src/utils/unsubscriber.ts new file mode 100644 index 0000000..e39b722 --- /dev/null +++ b/projects/ng-utils/src/utils/unsubscriber.ts @@ -0,0 +1,21 @@ +import { OnDestroy } from '@angular/core'; +import { Observable, Subject, Subscription } from 'rxjs'; + +const SUBS = Symbol('Subscriptions'); + +export abstract class Unsubscriber implements OnDestroy { + private [SUBS] = new Subscription(); + + protected onDestroy$: Observable = new Subject(); + + protected set subs(subscription: Subscription) { + this[SUBS].add(subscription); + } + + public ngOnDestroy() { + (this.onDestroy$ as Subject).next(); + (this.onDestroy$ as Subject).complete(); + + this[SUBS].unsubscribe(); + } +} diff --git a/projects/playground/src/app/app.component.html b/projects/playground/src/app/app.component.html index 5226d57..f3fe72f 100644 --- a/projects/playground/src/app/app.component.html +++ b/projects/playground/src/app/app.component.html @@ -1,20 +1,5 @@ - -
-

- Welcome to {{ title }}! -

- Angular Logo -
-

Here are some links to help you start:

- +
+ +
+
{{form.value | json}}
diff --git a/projects/playground/src/app/app.component.scss b/projects/playground/src/app/app.component.scss index e69de29..e7f0d84 100644 --- a/projects/playground/src/app/app.component.scss +++ b/projects/playground/src/app/app.component.scss @@ -0,0 +1,4 @@ +img { + width: 300px; + height: auto; +} diff --git a/projects/playground/src/app/app.component.ts b/projects/playground/src/app/app.component.ts index a59c71c..31dbd63 100644 --- a/projects/playground/src/app/app.component.ts +++ b/projects/playground/src/app/app.component.ts @@ -1,4 +1,6 @@ import { Component } from '@angular/core'; +import { interval } from 'rxjs'; +import { take, mapTo, startWith } from 'rxjs/operators'; @Component({ selector: 'app-root', @@ -6,5 +8,6 @@ import { Component } from '@angular/core'; styleUrls: ['./app.component.scss'] }) export class AppComponent { - title = 'playground'; + // tslint:disable-next-line:max-line-length + public image = interval(3000).pipe(take(1), mapTo('assets/angular.svg'), startWith('')); } diff --git a/projects/playground/src/app/app.module.ts b/projects/playground/src/app/app.module.ts index f657163..545c9ff 100644 --- a/projects/playground/src/app/app.module.ts +++ b/projects/playground/src/app/app.module.ts @@ -2,13 +2,17 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; +import { ImageInputModule } from '@ng-utils'; +import { FormsModule } from '@angular/forms'; @NgModule({ declarations: [ AppComponent ], imports: [ - BrowserModule + BrowserModule, + ImageInputModule, + FormsModule ], providers: [], bootstrap: [AppComponent] diff --git a/projects/playground/src/assets/angular.svg b/projects/playground/src/assets/angular.svg new file mode 100644 index 0000000..e365673 --- /dev/null +++ b/projects/playground/src/assets/angular.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 8024508..debd215 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,12 +19,12 @@ "dom" ], "paths": { - "ng-utils": [ - "dist/ng-utils" + "@ng-utils": [ + "projects/ng-utils/src/public-api.ts" ], - "ng-utils/*": [ - "dist/ng-utils/*" + "@ng-utils/*": [ + "projects/ng-utils/src/lib/*" ] } } -} \ No newline at end of file +}