From 6a269ec1e9d6695f8aed28fb3e0dc8d0de66b43c Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Mon, 26 Sep 2022 10:49:50 +0300 Subject: [PATCH] Move ng2-nouislider to project and remove dependency. Fix some issues regarding EventTarget and ng2-nouislider --- .../ng-package.json | 4 +- .../package.json | 3 +- .../app/configuration/configuration.module.ts | 2 +- .../settings/settings.component.html | 16 +- .../settings/settings.component.ts | 40 +-- .../src/app/contents/contents.component.html | 2 +- .../src/app/contents/contents.component.ts | 3 +- .../ng2-nouislider.component.ts | 304 ++++++++++++++++++ .../ng2-nouislider/ng2-nouislider.module.ts | 8 + 9 files changed, 347 insertions(+), 35 deletions(-) create mode 100644 interactive-mining-angular-frontend/src/app/ng2-nouislider/ng2-nouislider.component.ts create mode 100644 interactive-mining-angular-frontend/src/app/ng2-nouislider/ng2-nouislider.module.ts diff --git a/interactive-mining-angular-frontend/ng-package.json b/interactive-mining-angular-frontend/ng-package.json index 18dc203..5bba2de 100755 --- a/interactive-mining-angular-frontend/ng-package.json +++ b/interactive-mining-angular-frontend/ng-package.json @@ -1,6 +1,4 @@ { "$schema": "./node_modules/ng-packagr/ng-package.schema.json", - "whitelistedNonPeerDependencies": [ - "." - ] + "allowedNonPeerDependencies": ["."] } diff --git a/interactive-mining-angular-frontend/package.json b/interactive-mining-angular-frontend/package.json index 9c0b890..2965777 100755 --- a/interactive-mining-angular-frontend/package.json +++ b/interactive-mining-angular-frontend/package.json @@ -25,7 +25,6 @@ "core-js": "^2.4.1", "file-saver": "^2.0.2", "jquery": "^3.4.1", - "ng2-nouislider": "^1.8.2", "ngx-pagination": "^3.2.1", "nouislider": "^13.1.5", "rxjs": "^6.5.1", @@ -56,4 +55,4 @@ "ts-node": "~8.2.0", "typescript": "~4.6.4" } -} \ No newline at end of file +} diff --git a/interactive-mining-angular-frontend/src/app/configuration/configuration.module.ts b/interactive-mining-angular-frontend/src/app/configuration/configuration.module.ts index e2f1444..f3ad894 100755 --- a/interactive-mining-angular-frontend/src/app/configuration/configuration.module.ts +++ b/interactive-mining-angular-frontend/src/app/configuration/configuration.module.ts @@ -6,7 +6,7 @@ import { SettingsComponent } from './settings/settings.component'; import { ResultspreviewComponent } from './resultspreview/resultspreview.component'; import {ConfigurationService} from './configuration.service'; import {StepsnavbarModule} from '../stepsnvabar/stepsnavbar.module'; -import { NouisliderModule } from 'ng2-nouislider'; +import {NouisliderModule} from '../ng2-nouislider/ng2-nouislider.module'; @NgModule({ imports: [ diff --git a/interactive-mining-angular-frontend/src/app/configuration/settings/settings.component.html b/interactive-mining-angular-frontend/src/app/configuration/settings/settings.component.html index cc660e1..e0403b6 100755 --- a/interactive-mining-angular-frontend/src/app/configuration/settings/settings.component.html +++ b/interactive-mining-angular-frontend/src/app/configuration/settings/settings.component.html @@ -7,7 +7,7 @@ High
recall
- +
High
precision
@@ -121,19 +121,19 @@

Select among the following text preprocessing steps.

- +
- +
- +
- +
- +
@@ -173,13 +173,13 @@
- +
- +
diff --git a/interactive-mining-angular-frontend/src/app/configuration/settings/settings.component.ts b/interactive-mining-angular-frontend/src/app/configuration/settings/settings.component.ts index e3f9a2e..922ce41 100755 --- a/interactive-mining-angular-frontend/src/app/configuration/settings/settings.component.ts +++ b/interactive-mining-angular-frontend/src/app/configuration/settings/settings.component.ts @@ -191,45 +191,47 @@ export class SettingsComponent implements OnInit { } } - contextprevChange(value): void { + contextprevChange(eventTarget: EventTarget): void { + let value = +(eventTarget as HTMLInputElement).value; if (value < 0 || value > 20) { return; } - localStorage.setItem('contextprev', value); + localStorage.setItem('contextprev', value.toString()); this.getSettingsFromLocalStorage(); } - contextnextChange(value): void { + contextnextChange(eventTarget: EventTarget): void { + let value = +(eventTarget as HTMLInputElement).value; if (value < 0 || value > 20) { return; } - localStorage.setItem('contextnext', value); + localStorage.setItem('contextnext', value.toString()); this.getSettingsFromLocalStorage(); } - stopwordsCheckBoxChange(value: boolean): void { - localStorage.setItem('stopwords', value ? '1' : '0'); - this.settings.stopwords = value ? 1 : 0; + stopwordsCheckBoxChange(eventTarget: EventTarget): void { + localStorage.setItem('stopwords', (eventTarget as HTMLInputElement).checked ? '1' : '0'); + this.settings.stopwords = (eventTarget as HTMLInputElement).checked ? 1 : 0; } - punctuationCheckBoxChange(value: boolean): void { - localStorage.setItem('punctuation', value ? '1' : '0'); - this.settings.punctuation = value ? 1 : 0; + punctuationCheckBoxChange(eventTarget: EventTarget): void { + localStorage.setItem('punctuation', (eventTarget as HTMLInputElement).checked ? '1' : '0'); + this.settings.punctuation = (eventTarget as HTMLInputElement).checked ? 1 : 0; } - allLowercaseCheckBoxChange(value: boolean): void { - localStorage.setItem('allLowercase', value ? '1' : '0'); - this.settings.allLowercase = value ? 1 : 0; + allLowercaseCheckBoxChange(eventTarget: EventTarget): void { + localStorage.setItem('allLowercase', (eventTarget as HTMLInputElement).checked ? '1' : '0'); + this.settings.allLowercase = (eventTarget as HTMLInputElement).checked ? 1 : 0; } - lowercaseCheckBoxChange(value: boolean): void { - localStorage.setItem('lowercase', value ? '1' : '0'); - this.settings.lowercase = value ? 1 : 0; + lowercaseCheckBoxChange(eventTarget: EventTarget): void { + localStorage.setItem('lowercase', (eventTarget as HTMLInputElement).checked ? '1' : '0'); + this.settings.lowercase = (eventTarget as HTMLInputElement).checked ? 1 : 0; } - stemmingCheckBoxChange(value: boolean): void { - localStorage.setItem('stemming', value ? '1' : '0'); - this.settings.stemming = value ? 1 : 0; + stemmingCheckBoxChange(eventTarget: EventTarget): void { + localStorage.setItem('stemming', (eventTarget as HTMLInputElement).checked ? '1' : '0'); + this.settings.stemming = (eventTarget as HTMLInputElement).checked ? 1 : 0; } documentAreaChange(value: string): void { diff --git a/interactive-mining-angular-frontend/src/app/contents/contents.component.html b/interactive-mining-angular-frontend/src/app/contents/contents.component.html index e8d9aa5..d8ba1cf 100755 --- a/interactive-mining-angular-frontend/src/app/contents/contents.component.html +++ b/interactive-mining-angular-frontend/src/app/contents/contents.component.html @@ -59,7 +59,7 @@
- +
diff --git a/interactive-mining-angular-frontend/src/app/contents/contents.component.ts b/interactive-mining-angular-frontend/src/app/contents/contents.component.ts index 875cdb1..1b14129 100755 --- a/interactive-mining-angular-frontend/src/app/contents/contents.component.ts +++ b/interactive-mining-angular-frontend/src/app/contents/contents.component.ts @@ -87,7 +87,8 @@ export class ContentComponent implements OnInit { }); } - onFilesChange(file: File) { + onFilesChange(event: File | EventTarget) { + let file = (event instanceof File)?event:(event).files[0]; if (file !== null && file !== undefined) { const ext = file.name.split('.')[file.name.split('.').length - 1]; const allowedExtensions = ['tsv', 'txt']; diff --git a/interactive-mining-angular-frontend/src/app/ng2-nouislider/ng2-nouislider.component.ts b/interactive-mining-angular-frontend/src/app/ng2-nouislider/ng2-nouislider.component.ts new file mode 100644 index 0000000..eac9e64 --- /dev/null +++ b/interactive-mining-angular-frontend/src/app/ng2-nouislider/ng2-nouislider.component.ts @@ -0,0 +1,304 @@ +import * as noUiSlider from 'nouislider'; +import { + Component, + ElementRef, + EventEmitter, + forwardRef, + Input, + OnInit, + OnChanges, + Output, + Renderer2, + NgZone, + OnDestroy +} from '@angular/core'; +import { + ControlValueAccessor, + FormControl, + NG_VALUE_ACCESSOR +} from '@angular/forms'; + +export interface NouiFormatter { + to(value: number): string; + from(value: string): number; +} + +export class DefaultFormatter implements NouiFormatter { + to(value: number): string { + // formatting with http://stackoverflow.com/a/26463364/478584 + return String(parseFloat(parseFloat(String(value)).toFixed(2))); + }; + + from(value: string): number { + return parseFloat(value); + } +} + +@Component({ + selector: 'nouislider', + host: { + '[class.ng2-nouislider]': 'true' + }, + template: '
', + styles: [` + :host { + display: block; + margin-top: 1rem; + margin-bottom: 1rem; + } + `], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => NouisliderComponent), + multi: true + } + ] +}) +export class NouisliderComponent implements ControlValueAccessor, OnInit, OnChanges, OnDestroy { + + public slider: any; + public handles: any[]; + @Input() public disabled: boolean; // tslint:disable-line + @Input() public behaviour: string; + @Input() public connect: boolean[]; + @Input() public limit: number; + @Input() public min: number; + @Input() public max: number; + @Input() public snap: boolean; + @Input() public animate: boolean | boolean[]; + @Input() public range: any; + @Input() public step: number; + @Input() public format: NouiFormatter; + @Input() public pageSteps: number; + @Input() public config: any = {}; + @Input() public ngModel: number | number[]; + @Input() public keyboard: boolean; + @Input() public onKeydown: any; + @Input() public formControl: FormControl; + @Input() public tooltips: Array; + @Output() public change: EventEmitter = new EventEmitter(true); + @Output() public update: EventEmitter = new EventEmitter(true); + @Output() public slide: EventEmitter = new EventEmitter(true); + @Output() public set: EventEmitter = new EventEmitter(true); + @Output() public start: EventEmitter = new EventEmitter(true); + @Output() public end: EventEmitter = new EventEmitter(true); + private value: any; + private onChange: any = Function.prototype; + private cleanups: VoidFunction[] = []; + + constructor(private ngZone: NgZone, private el: ElementRef, private renderer : Renderer2) { } + + ngOnInit(): void { + let inputsConfig = JSON.parse( + JSON.stringify({ + behaviour: this.behaviour, + connect: this.connect, + limit: this.limit, + start: this.formControl !== undefined ? this.formControl.value : this.ngModel, + step: this.step, + pageSteps: this.pageSteps, + keyboard: this.keyboard, + onKeydown: this.onKeydown, + range: this.range || this.config.range || { min: this.min, max: this.max }, + tooltips: this.tooltips, + snap: this.snap, + animate: this.animate, + }), + ); + inputsConfig.tooltips = this.tooltips || this.config.tooltips; + inputsConfig.format = this.format || this.config.format || new DefaultFormatter(); + + this.ngZone.runOutsideAngular(() => { + this.slider = noUiSlider.create( + this.el.nativeElement.querySelector('div'), + Object.assign(this.config, inputsConfig) + ); + }); + + this.handles = [].slice.call(this.el.nativeElement.querySelectorAll('.noUi-handle')); + + if (this.config.keyboard) { + if (this.config.pageSteps === undefined) { + this.config.pageSteps = 10; + } + + for (const handle of this.handles) { + handle.setAttribute('tabindex', 0); + + const onKeydown = this.config.onKeydown || this.defaultKeyHandler; + + this.ngZone.runOutsideAngular(() => { + this.cleanups.push( + this.renderer.listen(handle, 'keydown', onKeydown), + this.renderer.listen(handle, 'click', () => { + handle.focus(); + }), + ); + }); + } + } + + this.slider.on('set', (values: string[], handle: number, unencoded: number[]) => { + this.eventHandler(this.set, values, handle, unencoded); + }); + + this.slider.on('update', (values: string[], handle: number, unencoded: number[]) => { + if (this.update.observers.length > 0) { + this.ngZone.run(() => { + this.update.emit(this.toValues(values)); + }); + } + }); + + this.slider.on('change', (values: string[], handle: number, unencoded: number[]) => { + if (this.change.observers.length > 0) { + this.ngZone.run(() => { + this.change.emit(this.toValues(values)); + }); + } + }); + + this.slider.on('slide', (values: string[], handle: number, unencoded: number[]) => { + this.eventHandler(this.slide, values, handle, unencoded); + }); + + this.slider.on('start', (values: string[], handle: number, unencoded: number[]) => { + if (this.start.observers.length > 0) { + this.ngZone.run(() => { + this.start.emit(this.toValues(values)); + }); + } + }); + + this.slider.on('end', (values: string[], handle: number, unencoded: number[]) => { + if (this.end.observers.length > 0) { + this.ngZone.run(() => { + this.end.emit(this.toValues(values)); + }); + } + }); + } + + ngOnChanges(changes: any) { + if (this.slider && (changes.min || changes.max || changes.step || changes.range)) { + this.ngZone.runOutsideAngular(() => { + setTimeout(() => { + this.slider.updateOptions({ + range: Object.assign({}, { + min: this.min, + max: this.max + }, this.range || {}), + step: this.step + }); + }); + }); + } + } + + ngOnDestroy(): void { + this.slider.destroy(); + + while (this.cleanups.length) { + this.cleanups.pop()(); + } + } + + toValues(values: string[]): any | any[] { + let v = values.map(this.config.format.from); + return (v.length == 1 ? v[0] : v); + } + + writeValue(value: any): void { + if (this.slider) { + this.ngZone.runOutsideAngular(() => { + setTimeout(() => { + this.slider.set(value); + }); + }); + } + } + + registerOnChange(fn: (value: any) => void) { + this.onChange = fn; + } + + registerOnTouched(fn: () => {}): void {} + + setDisabledState(isDisabled: boolean): void { + isDisabled + ? this.renderer.setAttribute(this.el.nativeElement.childNodes[0], 'disabled', 'true') + : this.renderer.removeAttribute(this.el.nativeElement.childNodes[0], 'disabled'); + } + + private eventHandler = (emitter: EventEmitter, values: string[], handle: number, unencoded: number[]) => { + let v = this.toValues(values); + let emitEvents = false; + if(this.value === undefined) { + this.value = v; + return; + } + if(Array.isArray(v) && this.value[handle] != v[handle]) { + emitEvents = true; + } + if(!Array.isArray(v) && this.value != v) { + emitEvents = true; + } + if(emitEvents) { + this.ngZone.run(() => { + if (emitter.observers.length > 0) { + emitter.emit(v); + } + this.onChange(v); + }); + } + if(Array.isArray(v)) { + this.value[handle] = v[handle]; + } else { + this.value = v; + } + } + + private defaultKeyHandler = (e: KeyboardEvent) => { + let stepSize: any[] = this.slider.steps(); + let index = parseInt((e.target).getAttribute('data-handle')); + let sign = 1; + let multiplier: number = 1; + let step = 0; + let delta = 0; + + switch ( e.which ) { + case 34: // PageDown + multiplier = this.config.pageSteps; + case 40: // ArrowDown + case 37: // ArrowLeft + sign = -1; + step = stepSize[index][0]; + e.preventDefault(); + break; + + case 33: // PageUp + multiplier = this.config.pageSteps; + case 38: // ArrowUp + case 39: // ArrowRight + step = stepSize[index][1]; + e.preventDefault(); + break; + + default: + break; + } + + delta = sign * multiplier * step; + let newValue: number | number[]; + + if(Array.isArray(this.value)) { + newValue = [].concat(this.value); + newValue[index] = newValue[index] + delta; + } else { + newValue = this.value + delta; + } + + this.slider.set(newValue); + } +} diff --git a/interactive-mining-angular-frontend/src/app/ng2-nouislider/ng2-nouislider.module.ts b/interactive-mining-angular-frontend/src/app/ng2-nouislider/ng2-nouislider.module.ts new file mode 100644 index 0000000..f428077 --- /dev/null +++ b/interactive-mining-angular-frontend/src/app/ng2-nouislider/ng2-nouislider.module.ts @@ -0,0 +1,8 @@ +import {NgModule} from "@angular/core"; +import {NouisliderComponent} from "./ng2-nouislider.component"; + +@NgModule({ + exports: [NouisliderComponent], + declarations: [NouisliderComponent], +}) +export class NouisliderModule { }