Merge branch 'master' into wiz/fix-npm-install-no-optional
This commit is contained in:
commit
0b0c0b458f
22
frontend/package-lock.json
generated
22
frontend/package-lock.json
generated
@ -36,7 +36,6 @@
|
|||||||
"echarts": "~5.3.2",
|
"echarts": "~5.3.2",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"lightweight-charts": "~3.8.0",
|
"lightweight-charts": "~3.8.0",
|
||||||
"ngx-bootrap-multiselect": "^2.0.0",
|
|
||||||
"ngx-echarts": "8.0.1",
|
"ngx-echarts": "8.0.1",
|
||||||
"ngx-infinite-scroll": "^10.0.1",
|
"ngx-infinite-scroll": "^10.0.1",
|
||||||
"qrcode": "1.5.0",
|
"qrcode": "1.5.0",
|
||||||
@ -12788,19 +12787,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
||||||
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
|
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
|
||||||
},
|
},
|
||||||
"node_modules/ngx-bootrap-multiselect": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ngx-bootrap-multiselect/-/ngx-bootrap-multiselect-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-GV/2MigCS5oi6P+zWtFSmq1TLWW1kcKsJNAXLP3hHXxmY3HgMKeUPk57o3T+YHje73JRp5reXMhEIlYuoOmoRg==",
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@angular/common": "^10.0.6",
|
|
||||||
"@angular/core": "^10.0.6",
|
|
||||||
"@angular/forms": "^10.0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ngx-echarts": {
|
"node_modules/ngx-echarts": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz",
|
||||||
@ -27418,14 +27404,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
||||||
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
|
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
|
||||||
},
|
},
|
||||||
"ngx-bootrap-multiselect": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ngx-bootrap-multiselect/-/ngx-bootrap-multiselect-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-GV/2MigCS5oi6P+zWtFSmq1TLWW1kcKsJNAXLP3hHXxmY3HgMKeUPk57o3T+YHje73JRp5reXMhEIlYuoOmoRg==",
|
|
||||||
"requires": {
|
|
||||||
"tslib": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ngx-echarts": {
|
"ngx-echarts": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz",
|
||||||
|
@ -90,7 +90,6 @@
|
|||||||
"echarts": "~5.3.2",
|
"echarts": "~5.3.2",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"lightweight-charts": "~3.8.0",
|
"lightweight-charts": "~3.8.0",
|
||||||
"ngx-bootrap-multiselect": "^2.0.0",
|
|
||||||
"ngx-echarts": "8.0.1",
|
"ngx-echarts": "8.0.1",
|
||||||
"ngx-infinite-scroll": "^10.0.1",
|
"ngx-infinite-scroll": "^10.0.1",
|
||||||
"qrcode": "1.5.0",
|
"qrcode": "1.5.0",
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div class="d-block float-right" id="filter">
|
<div class="d-block float-right" id="filter">
|
||||||
<form [formGroup]="radioGroupForm">
|
<form [formGroup]="radioGroupForm">
|
||||||
<ngx-bootrap-multiselect [options]="txTypeOptions" [settings]="txTypeDropdownSettings" [texts]="txTypeDropdownTexts" formControlName="txTypes"></ngx-bootrap-multiselect>
|
<ngx-bootstrap-multiselect [options]="txTypeOptions" [settings]="txTypeDropdownSettings" [texts]="txTypeDropdownTexts" formControlName="txTypes"></ngx-bootstrap-multiselect>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { BisqApiService } from '../bisq-api.service';
|
|||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { FormGroup, FormBuilder } from '@angular/forms';
|
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||||
import { Router, ActivatedRoute } from '@angular/router';
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'ngx-bootrap-multiselect';
|
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'src/app/components/ngx-bootstrap-multiselect/types'
|
||||||
import { WebsocketService } from 'src/app/services/websocket.service';
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BisqRoutingModule } from './bisq.routing.module';
|
import { BisqRoutingModule } from './bisq.routing.module';
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { NgxBootstrapMultiselectModule } from 'ngx-bootrap-multiselect';
|
|
||||||
|
|
||||||
import { LightweightChartsComponent } from './lightweight-charts/lightweight-charts.component';
|
import { LightweightChartsComponent } from './lightweight-charts/lightweight-charts.component';
|
||||||
import { LightweightChartsAreaComponent } from './lightweight-charts-area/lightweight-charts-area.component';
|
import { LightweightChartsAreaComponent } from './lightweight-charts-area/lightweight-charts-area.component';
|
||||||
@ -24,6 +23,10 @@ import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
|
|||||||
import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
|
import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
|
||||||
import { BisqTradesComponent } from './bisq-trades/bisq-trades.component';
|
import { BisqTradesComponent } from './bisq-trades/bisq-trades.component';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { AutofocusDirective } from '../components/ngx-bootstrap-multiselect/autofocus.directive';
|
||||||
|
import { MultiSelectSearchFilter } from '../components/ngx-bootstrap-multiselect/search-filter.pipe';
|
||||||
|
import { OffClickDirective } from '../components/ngx-bootstrap-multiselect/off-click.directive';
|
||||||
|
import { NgxDropdownMultiselectComponent } from '../components/ngx-bootstrap-multiselect/ngx-bootstrap-multiselect.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -44,16 +47,21 @@ import { CommonModule } from '@angular/common';
|
|||||||
BisqMarketComponent,
|
BisqMarketComponent,
|
||||||
BisqTradesComponent,
|
BisqTradesComponent,
|
||||||
BisqMainDashboardComponent,
|
BisqMainDashboardComponent,
|
||||||
|
NgxDropdownMultiselectComponent,
|
||||||
|
AutofocusDirective,
|
||||||
|
OffClickDirective,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
BisqRoutingModule,
|
BisqRoutingModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
NgxBootstrapMultiselectModule,
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
BisqApiService,
|
BisqApiService,
|
||||||
|
MultiSelectSearchFilter,
|
||||||
|
AutofocusDirective,
|
||||||
|
OffClickDirective,
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class BisqModule {
|
export class BisqModule {
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
import { Directive, ElementRef, Host, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[ssAutofocus]'
|
||||||
|
})
|
||||||
|
export class AutofocusDirective implements OnInit, OnChanges {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will set focus if set to falsy value or not set at all
|
||||||
|
*/
|
||||||
|
@Input() ssAutofocus: any;
|
||||||
|
|
||||||
|
get element(): { focus?: Function } {
|
||||||
|
return this.elemRef.nativeElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Host() private elemRef: ElementRef,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
const ssAutofocusChange = changes.ssAutofocus;
|
||||||
|
|
||||||
|
if (ssAutofocusChange && !ssAutofocusChange.isFirstChange()) {
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
if (this.ssAutofocus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.element.focus && this.element.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
a {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-inline {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-toggle .caret {
|
||||||
|
margin-left: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chunkydropdown-menu {
|
||||||
|
min-width: 20em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chunkyrow {
|
||||||
|
line-height: 2;
|
||||||
|
margin-left: 1em;
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
width:3.8em;
|
||||||
|
height:3.8em;
|
||||||
|
display:block;
|
||||||
|
-webkit-transition: all 0.125s linear;
|
||||||
|
-moz-transition: all 0.125s linear;
|
||||||
|
-o-transition: all 0.125s linear;
|
||||||
|
transition: all 0.125s linear;
|
||||||
|
margin-left: 0.125em;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slideron {
|
||||||
|
margin-left: 1.35em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content_wrapper{
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
padding: 0px 5px 5px 5px;
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
<div *ngIf="options" class="dropdown" [ngClass]="settings.containerClasses" [class.open]="isVisible" (offClick)="clickedOutside()">
|
||||||
|
<button type="button" class="dropdown-toggle" [ngClass]="settings.buttonClasses" (click)="toggleDropdown($event)" [disabled]="disabled"
|
||||||
|
[ssAutofocus]="!focusBack">
|
||||||
|
{{ title }}
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<div #scroller *ngIf="isVisible" class="dropdown-menu" [ngClass]="{'chunkydropdown-menu': settings.checkedStyle == 'visual' }"
|
||||||
|
(scroll)="settings.isLazyLoad ? checkScrollPosition($event) : null" (wheel)="settings.stopScrollPropagation ? checkScrollPropagation($event, scroller) : null"
|
||||||
|
[class.pull-right]="settings.pullRight" [class.dropdown-menu-right]="settings.pullRight" [style.max-height]="settings.maxHeight"
|
||||||
|
style="display: block; height: auto; overflow-y: auto;" (keydown.tab)="focusItem(1, $event)" (keydown.shift.tab)="focusItem(-1, $event)">
|
||||||
|
<div class="input-group search-container" *ngIf="settings.enableSearch && (renderFilteredOptions.length > 1 || filterControl.value.length > 0)">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text" id="basic-addon1">
|
||||||
|
<i class="fa fa-search" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" ssAutofocus [formControl]="filterControl" [placeholder]="texts.searchPlaceholder"
|
||||||
|
class="form-control">
|
||||||
|
<div class="input-group-append" *ngIf="filterControl.value.length>0">
|
||||||
|
<button class="btn btn-default btn-secondary" type="button" (click)="clearSearch($event)">
|
||||||
|
<i class="fa fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a role="menuitem" href="javascript:;" tabindex="-1" class="dropdown-item check-control check-control-check" *ngIf="settings.showCheckAll && !disabledSelection && renderFilteredOptions.length > 1"
|
||||||
|
(click)="checkAll()">
|
||||||
|
<span style="width: 16px;"><span [ngClass]="{'glyphicon glyphicon-ok': settings.checkedStyle !== 'fontawesome','fa fa-check': settings.checkedStyle === 'fontawesome'}"></span></span>
|
||||||
|
{{ texts.checkAll }}
|
||||||
|
</a>
|
||||||
|
<a role="menuitem" href="javascript:;" tabindex="-1" class="dropdown-item check-control check-control-uncheck" *ngIf="settings.showUncheckAll && !disabledSelection && renderFilteredOptions.length > 1"
|
||||||
|
(click)="uncheckAll()">
|
||||||
|
<span style="width: 16px;"><span [ngClass]="{'glyphicon glyphicon-remove': settings.checkedStyle !== 'fontawesome','fa fa-times': settings.checkedStyle === 'fontawesome'}"></span></span>
|
||||||
|
{{ texts.uncheckAll }}
|
||||||
|
</a>
|
||||||
|
<a *ngIf="settings.showCheckAll || settings.showUncheckAll" href="javascript:;" class="dropdown-divider divider"></a>
|
||||||
|
<a *ngIf="!renderItems" href="javascript:;" class="dropdown-item empty">{{ texts.searchNoRenderText }}</a>
|
||||||
|
<a *ngIf="renderItems && !renderFilteredOptions.length" href="javascript:;" class="dropdown-item empty">{{ texts.searchEmptyResult }}</a>
|
||||||
|
<a class="dropdown-item" href="javascript:;" *ngFor="let option of renderFilteredOptions; trackBy: trackById" [class.active]="isSelected(option)"
|
||||||
|
[ngStyle]="getItemStyle(option)" [ngClass]="option.classes" [class.dropdown-header]="option.isLabel" [ssAutofocus]="option !== focusedItem"
|
||||||
|
tabindex="-1" (click)="setSelected($event, option)" (keydown.space)="setSelected($event, option)" (keydown.enter)="setSelected($event, option)">
|
||||||
|
<span *ngIf="!option.isLabel; else label" role="menuitem" tabindex="-1" [style.padding-left]="this.parents.length>0&&this.parents.indexOf(option.id)<0&&'30px'"
|
||||||
|
[ngStyle]="getItemStyleSelectionDisabled()">
|
||||||
|
<ng-container [ngSwitch]="settings.checkedStyle">
|
||||||
|
<input *ngSwitchCase="'checkboxes'" type="checkbox" [checked]="isSelected(option)" (click)="preventCheckboxCheck($event, option)"
|
||||||
|
[disabled]="isCheckboxDisabled(option)" [ngStyle]="getItemStyleSelectionDisabled()" />
|
||||||
|
<span *ngSwitchCase="'glyphicon'" style="width: 16px;" class="glyphicon" [class.glyphicon-ok]="isSelected(option)" [class.glyphicon-lock]="isCheckboxDisabled(option)"></span>
|
||||||
|
<span *ngSwitchCase="'fontawesome'" style="width: 16px;display: inline-block;">
|
||||||
|
<span *ngIf="isSelected(option)"><i class="fa fa-check" aria-hidden="true"></i></span>
|
||||||
|
<span *ngIf="isCheckboxDisabled(option)"><i class="fa fa-lock" aria-hidden="true"></i></span>
|
||||||
|
</span>
|
||||||
|
<span *ngSwitchCase="'visual'" style="display:block;float:left; border-radius: 0.2em; border: 0.1em solid rgba(44, 44, 44, 0.63);background:rgba(0, 0, 0, 0.1);width: 5.5em;">
|
||||||
|
<div class="slider" [ngClass]="{'slideron': isSelected(option)}">
|
||||||
|
<img *ngIf="option.image != null" [src]="option.image" style="height: 100%; width: 100%; object-fit: contain" />
|
||||||
|
<div *ngIf="option.image == null" style="height: 100%; width: 100%;text-align: center; display: table; background-color:rgba(0, 0, 0, 0.74)">
|
||||||
|
<div class="content_wrapper">
|
||||||
|
<span style="font-size:3em;color:white" class="glyphicon glyphicon-eye-close"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</ng-container>
|
||||||
|
<span [ngClass]="{'chunkyrow': settings.checkedStyle == 'visual' }" [class.disabled]="isCheckboxDisabled(option)" [ngClass]="settings.itemClasses"
|
||||||
|
[style.font-weight]="this.parents.indexOf(option.id)>=0?'bold':'normal'">
|
||||||
|
{{ option.name }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<ng-template #label>
|
||||||
|
<span [class.disabled]="isCheckboxDisabled(option)">{{ option.name }}</span>
|
||||||
|
</ng-template>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,710 @@
|
|||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
DoCheck,
|
||||||
|
EventEmitter,
|
||||||
|
forwardRef,
|
||||||
|
Input,
|
||||||
|
IterableDiffers,
|
||||||
|
OnChanges,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
Output,
|
||||||
|
SimpleChanges,
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AbstractControl,
|
||||||
|
ControlValueAccessor,
|
||||||
|
FormBuilder,
|
||||||
|
FormControl,
|
||||||
|
NG_VALUE_ACCESSOR,
|
||||||
|
Validator,
|
||||||
|
} from '@angular/forms';
|
||||||
|
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
import { MultiSelectSearchFilter } from './search-filter.pipe';
|
||||||
|
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts, } from './types';
|
||||||
|
import { Subject, Observable } from 'rxjs';
|
||||||
|
|
||||||
|
const MULTISELECT_VALUE_ACCESSOR: any = {
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => NgxDropdownMultiselectComponent),
|
||||||
|
multi: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// tslint:disable-next-line: no-conflicting-lifecycle
|
||||||
|
@Component({
|
||||||
|
selector: 'ngx-bootstrap-multiselect',
|
||||||
|
templateUrl: './ngx-bootstrap-multiselect.component.html',
|
||||||
|
styleUrls: ['./ngx-bootstrap-multiselect.component.css'],
|
||||||
|
providers: [MULTISELECT_VALUE_ACCESSOR, MultiSelectSearchFilter],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class NgxDropdownMultiselectComponent implements OnInit,
|
||||||
|
OnChanges,
|
||||||
|
DoCheck,
|
||||||
|
OnDestroy,
|
||||||
|
ControlValueAccessor,
|
||||||
|
Validator {
|
||||||
|
|
||||||
|
private localIsVisible = false;
|
||||||
|
private workerDocClicked = false;
|
||||||
|
|
||||||
|
filterControl: FormControl = this.fb.control('');
|
||||||
|
|
||||||
|
@Input() options: Array<IMultiSelectOption>;
|
||||||
|
@Input() settings: IMultiSelectSettings;
|
||||||
|
@Input() texts: IMultiSelectTexts;
|
||||||
|
@Input() disabled = false;
|
||||||
|
@Input() disabledSelection = false;
|
||||||
|
@Input() searchFunction: (str: string) => RegExp = this._escapeRegExp;
|
||||||
|
|
||||||
|
@Output() selectionLimitReached = new EventEmitter();
|
||||||
|
@Output() dropdownClosed = new EventEmitter();
|
||||||
|
@Output() dropdownOpened = new EventEmitter();
|
||||||
|
@Output() added = new EventEmitter();
|
||||||
|
@Output() removed = new EventEmitter();
|
||||||
|
@Output() lazyLoad = new EventEmitter();
|
||||||
|
@Output() filter: Observable<string> = this.filterControl.valueChanges;
|
||||||
|
|
||||||
|
get focusBack(): boolean {
|
||||||
|
return this.settings.focusBack && this._focusBack;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyed$ = new Subject<any>();
|
||||||
|
|
||||||
|
filteredOptions: IMultiSelectOption[] = [];
|
||||||
|
lazyLoadOptions: IMultiSelectOption[] = [];
|
||||||
|
renderFilteredOptions: IMultiSelectOption[] = [];
|
||||||
|
model: any[] = [];
|
||||||
|
prevModel: any[] = [];
|
||||||
|
parents: any[];
|
||||||
|
title: string;
|
||||||
|
differ: any;
|
||||||
|
numSelected = 0;
|
||||||
|
set isVisible(val: boolean) {
|
||||||
|
this.localIsVisible = val;
|
||||||
|
this.workerDocClicked = val ? false : this.workerDocClicked;
|
||||||
|
}
|
||||||
|
get isVisible(): boolean {
|
||||||
|
return this.localIsVisible;
|
||||||
|
}
|
||||||
|
renderItems = true;
|
||||||
|
checkAllSearchRegister = new Set();
|
||||||
|
checkAllStatus = false;
|
||||||
|
loadedValueIds = [];
|
||||||
|
_focusBack = false;
|
||||||
|
focusedItem: IMultiSelectOption | undefined;
|
||||||
|
|
||||||
|
defaultSettings: IMultiSelectSettings = {
|
||||||
|
closeOnClickOutside: true,
|
||||||
|
pullRight: false,
|
||||||
|
enableSearch: false,
|
||||||
|
searchRenderLimit: 0,
|
||||||
|
searchRenderAfter: 1,
|
||||||
|
searchMaxLimit: 0,
|
||||||
|
searchMaxRenderedItems: 0,
|
||||||
|
checkedStyle: 'checkboxes',
|
||||||
|
buttonClasses: 'btn btn-primary dropdown-toggle',
|
||||||
|
containerClasses: 'dropdown-inline',
|
||||||
|
selectionLimit: 0,
|
||||||
|
minSelectionLimit: 0,
|
||||||
|
closeOnSelect: false,
|
||||||
|
autoUnselect: false,
|
||||||
|
showCheckAll: false,
|
||||||
|
showUncheckAll: false,
|
||||||
|
fixedTitle: false,
|
||||||
|
dynamicTitleMaxItems: 3,
|
||||||
|
maxHeight: '300px',
|
||||||
|
isLazyLoad: false,
|
||||||
|
stopScrollPropagation: false,
|
||||||
|
loadViewDistance: 1,
|
||||||
|
selectAddedValues: false,
|
||||||
|
ignoreLabels: false,
|
||||||
|
maintainSelectionOrderInTitle: false,
|
||||||
|
focusBack: true
|
||||||
|
};
|
||||||
|
defaultTexts: IMultiSelectTexts = {
|
||||||
|
checkAll: 'Select all',
|
||||||
|
uncheckAll: 'Unselect all',
|
||||||
|
checked: 'selected',
|
||||||
|
checkedPlural: 'selected',
|
||||||
|
searchPlaceholder: 'Search...',
|
||||||
|
searchEmptyResult: 'Nothing found...',
|
||||||
|
searchNoRenderText: 'Type in search box to see results...',
|
||||||
|
defaultTitle: 'Select',
|
||||||
|
allSelected: 'All selected',
|
||||||
|
};
|
||||||
|
|
||||||
|
get searchLimit(): number | undefined {
|
||||||
|
return this.settings.searchRenderLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
get searchRenderAfter(): number | undefined {
|
||||||
|
return this.settings.searchRenderAfter;
|
||||||
|
}
|
||||||
|
|
||||||
|
get searchLimitApplied(): boolean {
|
||||||
|
return this.searchLimit > 0 && this.options.length > this.searchLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private searchFilter: MultiSelectSearchFilter,
|
||||||
|
differs: IterableDiffers,
|
||||||
|
private cdRef: ChangeDetectorRef
|
||||||
|
) {
|
||||||
|
this.differ = differs.find([]).create(null);
|
||||||
|
this.settings = this.defaultSettings;
|
||||||
|
this.texts = this.defaultTexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
clickedOutside(): void {
|
||||||
|
if (!this.isVisible || !this.settings.closeOnClickOutside) { return; }
|
||||||
|
|
||||||
|
this.isVisible = false;
|
||||||
|
this._focusBack = true;
|
||||||
|
this.dropdownClosed.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemStyle(option: IMultiSelectOption): any {
|
||||||
|
const style = {};
|
||||||
|
if (!option.isLabel) {
|
||||||
|
style['cursor'] = 'pointer';
|
||||||
|
}
|
||||||
|
if (option.disabled) {
|
||||||
|
style['cursor'] = 'default';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemStyleSelectionDisabled(): any {
|
||||||
|
if (this.disabledSelection) {
|
||||||
|
return { cursor: 'default' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.title = this.texts.defaultTitle || '';
|
||||||
|
|
||||||
|
this.filterControl.valueChanges.pipe(takeUntil(this.destroyed$)).subscribe(() => {
|
||||||
|
this.updateRenderItems();
|
||||||
|
if (this.settings.isLazyLoad) {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (changes['options']) {
|
||||||
|
this.options = this.options || [];
|
||||||
|
this.parents = this.options
|
||||||
|
.filter(option => typeof option.parentId === 'number')
|
||||||
|
.map(option => option.parentId);
|
||||||
|
this.updateRenderItems();
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.settings.isLazyLoad &&
|
||||||
|
this.settings.selectAddedValues &&
|
||||||
|
this.loadedValueIds.length === 0
|
||||||
|
) {
|
||||||
|
this.loadedValueIds = this.loadedValueIds.concat(
|
||||||
|
changes.options.currentValue.map(value => value.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this.settings.isLazyLoad &&
|
||||||
|
this.settings.selectAddedValues &&
|
||||||
|
changes.options.previousValue
|
||||||
|
) {
|
||||||
|
const addedValues = changes.options.currentValue.filter(
|
||||||
|
value => this.loadedValueIds.indexOf(value.id) === -1
|
||||||
|
);
|
||||||
|
this.loadedValueIds.concat(addedValues.map(value => value.id));
|
||||||
|
if (this.checkAllStatus) {
|
||||||
|
this.addChecks(addedValues);
|
||||||
|
} else if (this.checkAllSearchRegister.size > 0) {
|
||||||
|
this.checkAllSearchRegister.forEach((searchValue: string) =>
|
||||||
|
this.addChecks(this.applyFilters(addedValues, searchValue))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.texts) {
|
||||||
|
this.updateTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fireModelChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes['settings']) {
|
||||||
|
this.settings = { ...this.defaultSettings, ...this.settings };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes['texts']) {
|
||||||
|
this.texts = { ...this.defaultTexts, ...this.texts };
|
||||||
|
if (!changes['texts'].isFirstChange()) { this.updateTitle(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.destroyed$.next(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRenderItems() {
|
||||||
|
this.renderItems =
|
||||||
|
!this.searchLimitApplied ||
|
||||||
|
this.filterControl.value.length >= this.searchRenderAfter;
|
||||||
|
this.filteredOptions = this.applyFilters(
|
||||||
|
this.options,
|
||||||
|
this.settings.isLazyLoad ? '' : this.filterControl.value
|
||||||
|
);
|
||||||
|
this.renderFilteredOptions = this.renderItems ? this.filteredOptions : [];
|
||||||
|
this.focusedItem = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyFilters(options: IMultiSelectOption[], value: string): IMultiSelectOption[] {
|
||||||
|
return this.searchFilter.transform(
|
||||||
|
options,
|
||||||
|
value,
|
||||||
|
this.settings.searchMaxLimit,
|
||||||
|
this.settings.searchMaxRenderedItems,
|
||||||
|
this.searchFunction
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fireModelChange(): void {
|
||||||
|
if (this.model != this.prevModel) {
|
||||||
|
this.prevModel = this.model;
|
||||||
|
this.onModelChange(this.model);
|
||||||
|
this.onModelTouched();
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onModelChange: Function = (_: any) => { };
|
||||||
|
onModelTouched: Function = () => { };
|
||||||
|
|
||||||
|
writeValue(value: any): void {
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
this.model = Array.isArray(value) ? value : [value];
|
||||||
|
this.ngDoCheck();
|
||||||
|
} else {
|
||||||
|
this.model = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnChange(fn: Function): void {
|
||||||
|
this.onModelChange = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnTouched(fn: Function): void {
|
||||||
|
this.onModelTouched = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisabledState(isDisabled: boolean) {
|
||||||
|
this.disabled = isDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngDoCheck() {
|
||||||
|
const changes = this.differ.diff(this.model);
|
||||||
|
if (changes) {
|
||||||
|
this.updateNumSelected();
|
||||||
|
this.updateTitle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(_c: AbstractControl): { [key: string]: any } {
|
||||||
|
if (this.model && this.model.length) {
|
||||||
|
return {
|
||||||
|
required: {
|
||||||
|
valid: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.filter(o => this.model.indexOf(o.id) && !o.disabled).length === 0) {
|
||||||
|
return {
|
||||||
|
selection: {
|
||||||
|
valid: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnValidatorChange(_fn: () => void): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSearch(event: Event) {
|
||||||
|
this.maybeStopPropagation(event);
|
||||||
|
this.filterControl.setValue('');
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDropdown(e?: Event) {
|
||||||
|
if (this.isVisible) {
|
||||||
|
this._focusBack = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isVisible = !this.isVisible;
|
||||||
|
this.isVisible ? this.dropdownOpened.emit() : this.dropdownClosed.emit();
|
||||||
|
this.focusedItem = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDropdown(e?: Event) {
|
||||||
|
this.isVisible = true;
|
||||||
|
this.toggleDropdown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSelected(option: IMultiSelectOption): boolean {
|
||||||
|
return this.model && this.model.indexOf(option.id) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelected(_event: Event, option: IMultiSelectOption) {
|
||||||
|
if (option.isLabel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.disabledSelection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.maybeStopPropagation(_event);
|
||||||
|
this.maybePreventDefault(_event);
|
||||||
|
const index = this.model.indexOf(option.id);
|
||||||
|
const isAtSelectionLimit =
|
||||||
|
this.settings.selectionLimit > 0 &&
|
||||||
|
this.model.length >= this.settings.selectionLimit;
|
||||||
|
const removeItem = (idx, id): void => {
|
||||||
|
this.model.splice(idx, 1);
|
||||||
|
this.removed.emit(id);
|
||||||
|
if (
|
||||||
|
this.settings.isLazyLoad &&
|
||||||
|
this.lazyLoadOptions.some(val => val.id === id)
|
||||||
|
) {
|
||||||
|
this.lazyLoadOptions.splice(
|
||||||
|
this.lazyLoadOptions.indexOf(
|
||||||
|
this.lazyLoadOptions.find(val => val.id === id)
|
||||||
|
),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
if (
|
||||||
|
this.settings.minSelectionLimit === undefined ||
|
||||||
|
this.numSelected > this.settings.minSelectionLimit
|
||||||
|
) {
|
||||||
|
removeItem(index, option.id);
|
||||||
|
}
|
||||||
|
const parentIndex =
|
||||||
|
option.parentId && this.model.indexOf(option.parentId);
|
||||||
|
if (parentIndex > -1) {
|
||||||
|
removeItem(parentIndex, option.parentId);
|
||||||
|
} else if (this.parents.indexOf(option.id) > -1) {
|
||||||
|
this.options
|
||||||
|
.filter(
|
||||||
|
child =>
|
||||||
|
this.model.indexOf(child.id) > -1 &&
|
||||||
|
child.parentId === option.id
|
||||||
|
)
|
||||||
|
.forEach(child =>
|
||||||
|
removeItem(this.model.indexOf(child.id), child.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (isAtSelectionLimit && !this.settings.autoUnselect) {
|
||||||
|
this.selectionLimitReached.emit(this.model.length);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const addItem = (id): void => {
|
||||||
|
this.model.push(id);
|
||||||
|
this.added.emit(id);
|
||||||
|
if (
|
||||||
|
this.settings.isLazyLoad &&
|
||||||
|
!this.lazyLoadOptions.some(val => val.id === id)
|
||||||
|
) {
|
||||||
|
this.lazyLoadOptions.push(option);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addItem(option.id);
|
||||||
|
if (!isAtSelectionLimit) {
|
||||||
|
if (option.parentId && !this.settings.ignoreLabels) {
|
||||||
|
const children = this.options.filter(
|
||||||
|
child =>
|
||||||
|
child.id !== option.id && child.parentId === option.parentId
|
||||||
|
);
|
||||||
|
if (children.every(child => this.model.indexOf(child.id) > -1)) {
|
||||||
|
addItem(option.parentId);
|
||||||
|
}
|
||||||
|
} else if (this.parents.indexOf(option.id) > -1) {
|
||||||
|
const children = this.options.filter(
|
||||||
|
child =>
|
||||||
|
this.model.indexOf(child.id) < 0 && child.parentId === option.id
|
||||||
|
);
|
||||||
|
children.forEach(child => addItem(child.id));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
removeItem(0, this.model[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.settings.closeOnSelect) {
|
||||||
|
this.toggleDropdown();
|
||||||
|
}
|
||||||
|
this.model = this.model.slice();
|
||||||
|
this.fireModelChange();
|
||||||
|
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNumSelected() {
|
||||||
|
this.numSelected =
|
||||||
|
this.model.filter(id => this.parents.indexOf(id) < 0).length || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTitle() {
|
||||||
|
let numSelectedOptions = this.options.length;
|
||||||
|
if (this.settings.ignoreLabels) {
|
||||||
|
numSelectedOptions = this.options.filter(
|
||||||
|
(option: IMultiSelectOption) => !option.isLabel
|
||||||
|
).length;
|
||||||
|
}
|
||||||
|
if (this.numSelected === 0 || this.settings.fixedTitle) {
|
||||||
|
this.title = this.texts ? this.texts.defaultTitle : '';
|
||||||
|
} else if (
|
||||||
|
this.settings.displayAllSelectedText &&
|
||||||
|
this.model.length === numSelectedOptions
|
||||||
|
) {
|
||||||
|
this.title = this.texts ? this.texts.allSelected : '';
|
||||||
|
} else if (
|
||||||
|
this.settings.dynamicTitleMaxItems &&
|
||||||
|
this.settings.dynamicTitleMaxItems >= this.numSelected
|
||||||
|
) {
|
||||||
|
const useOptions =
|
||||||
|
this.settings.isLazyLoad && this.lazyLoadOptions.length
|
||||||
|
? this.lazyLoadOptions
|
||||||
|
: this.options;
|
||||||
|
|
||||||
|
let titleSelections: Array<IMultiSelectOption>;
|
||||||
|
|
||||||
|
if (this.settings.maintainSelectionOrderInTitle) {
|
||||||
|
const optionIds = useOptions.map((selectOption: IMultiSelectOption, idx: number) => selectOption.id);
|
||||||
|
titleSelections = this.model
|
||||||
|
.map((selectedId) => optionIds.indexOf(selectedId))
|
||||||
|
.filter((optionIndex) => optionIndex > -1)
|
||||||
|
.map((optionIndex) => useOptions[optionIndex]);
|
||||||
|
} else {
|
||||||
|
titleSelections = useOptions.filter((option: IMultiSelectOption) => this.model.indexOf(option.id) > -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.title = titleSelections.map((option: IMultiSelectOption) => option.name).join(', ');
|
||||||
|
} else {
|
||||||
|
this.title =
|
||||||
|
this.numSelected +
|
||||||
|
' ' +
|
||||||
|
(this.numSelected === 1
|
||||||
|
? this.texts.checked
|
||||||
|
: this.texts.checkedPlural);
|
||||||
|
}
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
searchFilterApplied() {
|
||||||
|
return (
|
||||||
|
this.settings.enableSearch &&
|
||||||
|
this.filterControl.value &&
|
||||||
|
this.filterControl.value.length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addChecks(options) {
|
||||||
|
const checkedOptions = options
|
||||||
|
.filter((option: IMultiSelectOption) => {
|
||||||
|
if (
|
||||||
|
!option.disabled &&
|
||||||
|
(
|
||||||
|
this.model.indexOf(option.id) === -1 &&
|
||||||
|
!(this.settings.ignoreLabels && option.isLabel)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.added.emit(option.id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
.map((option: IMultiSelectOption) => option.id);
|
||||||
|
|
||||||
|
this.model = this.model.concat(checkedOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAll(): void {
|
||||||
|
if (!this.disabledSelection) {
|
||||||
|
this.addChecks(
|
||||||
|
!this.searchFilterApplied() ? this.options : this.filteredOptions
|
||||||
|
);
|
||||||
|
if (this.settings.isLazyLoad && this.settings.selectAddedValues) {
|
||||||
|
if (this.searchFilterApplied() && !this.checkAllStatus) {
|
||||||
|
this.checkAllSearchRegister.add(this.filterControl.value);
|
||||||
|
} else {
|
||||||
|
this.checkAllSearchRegister.clear();
|
||||||
|
this.checkAllStatus = true;
|
||||||
|
}
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
this.fireModelChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uncheckAll(): void {
|
||||||
|
if (!this.disabledSelection) {
|
||||||
|
const checkedOptions = this.model;
|
||||||
|
let unCheckedOptions = !this.searchFilterApplied()
|
||||||
|
? this.model
|
||||||
|
: this.filteredOptions.map((option: IMultiSelectOption) => option.id);
|
||||||
|
// set unchecked options only to the ones that were checked
|
||||||
|
unCheckedOptions = checkedOptions.filter(item => unCheckedOptions.indexOf(item) > -1);
|
||||||
|
this.model = this.model.filter((id: number) => {
|
||||||
|
if (
|
||||||
|
(unCheckedOptions.indexOf(id) < 0 &&
|
||||||
|
this.settings.minSelectionLimit === undefined) ||
|
||||||
|
unCheckedOptions.indexOf(id) < this.settings.minSelectionLimit
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.removed.emit(id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (this.settings.isLazyLoad && this.settings.selectAddedValues) {
|
||||||
|
if (this.searchFilterApplied()) {
|
||||||
|
if (this.checkAllSearchRegister.has(this.filterControl.value)) {
|
||||||
|
this.checkAllSearchRegister.delete(this.filterControl.value);
|
||||||
|
this.checkAllSearchRegister.forEach(function(searchTerm) {
|
||||||
|
const filterOptions = this.applyFilters(this.options.filter(option => unCheckedOptions.indexOf(option.id) > -1), searchTerm);
|
||||||
|
this.addChecks(filterOptions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.checkAllSearchRegister.clear();
|
||||||
|
this.checkAllStatus = false;
|
||||||
|
}
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
this.fireModelChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preventCheckboxCheck(event: Event, option: IMultiSelectOption): void {
|
||||||
|
if (
|
||||||
|
option.disabled ||
|
||||||
|
(
|
||||||
|
this.settings.selectionLimit &&
|
||||||
|
!this.settings.autoUnselect &&
|
||||||
|
this.model.length >= this.settings.selectionLimit &&
|
||||||
|
this.model.indexOf(option.id) === -1 &&
|
||||||
|
this.maybePreventDefault(event)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.maybePreventDefault(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isCheckboxDisabled(option?: IMultiSelectOption): boolean {
|
||||||
|
return this.disabledSelection || option && option.disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkScrollPosition(ev): void {
|
||||||
|
const scrollTop = ev.target.scrollTop;
|
||||||
|
const scrollHeight = ev.target.scrollHeight;
|
||||||
|
const scrollElementHeight = ev.target.clientHeight;
|
||||||
|
const roundingPixel = 1;
|
||||||
|
const gutterPixel = 1;
|
||||||
|
|
||||||
|
if (
|
||||||
|
scrollTop >=
|
||||||
|
scrollHeight -
|
||||||
|
(1 + this.settings.loadViewDistance) * scrollElementHeight -
|
||||||
|
roundingPixel -
|
||||||
|
gutterPixel
|
||||||
|
) {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkScrollPropagation(ev, element): void {
|
||||||
|
const scrollTop = element.scrollTop;
|
||||||
|
const scrollHeight = element.scrollHeight;
|
||||||
|
const scrollElementHeight = element.clientHeight;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(ev.deltaY > 0 && scrollTop + scrollElementHeight >= scrollHeight) ||
|
||||||
|
(ev.deltaY < 0 && scrollTop <= 0)
|
||||||
|
) {
|
||||||
|
ev = ev || window.event;
|
||||||
|
this.maybePreventDefault(ev);
|
||||||
|
ev.returnValue = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trackById(idx: number, selectOption: IMultiSelectOption): void {
|
||||||
|
return selectOption.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
load(): void {
|
||||||
|
this.lazyLoad.emit({
|
||||||
|
length: this.options.length,
|
||||||
|
filter: this.filterControl.value,
|
||||||
|
checkAllSearches: this.checkAllSearchRegister,
|
||||||
|
checkAllStatus: this.checkAllStatus,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
focusItem(dir: number, e?: Event): void {
|
||||||
|
if (!this.isVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.maybePreventDefault(e);
|
||||||
|
|
||||||
|
const idx = this.filteredOptions.indexOf(this.focusedItem);
|
||||||
|
|
||||||
|
if (idx === -1) {
|
||||||
|
this.focusedItem = this.filteredOptions[0];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextIdx = idx + dir;
|
||||||
|
const newIdx =
|
||||||
|
nextIdx < 0
|
||||||
|
? this.filteredOptions.length - 1
|
||||||
|
: nextIdx % this.filteredOptions.length;
|
||||||
|
|
||||||
|
this.focusedItem = this.filteredOptions[newIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
private maybePreventDefault(e?: Event): void {
|
||||||
|
if (e && e.preventDefault) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private maybeStopPropagation(e?: Event): void {
|
||||||
|
if (e && e.stopPropagation) {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _escapeRegExp(str: string): RegExp {
|
||||||
|
const regExpStr = str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
|
||||||
|
return new RegExp(regExpStr, 'i');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
import { Directive, HostListener } from '@angular/core';
|
||||||
|
import { EventEmitter } from '@angular/core';
|
||||||
|
import { Output } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
// tslint:disable-next-line:directive-selector
|
||||||
|
selector: '[offClick]',
|
||||||
|
})
|
||||||
|
|
||||||
|
export class OffClickDirective {
|
||||||
|
@Output('offClick') onOffClick = new EventEmitter<any>();
|
||||||
|
|
||||||
|
private _clickEvent: MouseEvent;
|
||||||
|
private _touchEvent: TouchEvent;
|
||||||
|
|
||||||
|
@HostListener('click', ['$event'])
|
||||||
|
public onClick(event: MouseEvent): void {
|
||||||
|
this._clickEvent = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('touchstart', ['$event'])
|
||||||
|
public onTouch(event: TouchEvent): void {
|
||||||
|
this._touchEvent = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:click', ['$event'])
|
||||||
|
public onDocumentClick(event: MouseEvent): void {
|
||||||
|
if (event !== this._clickEvent) {
|
||||||
|
this.onOffClick.emit(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:touchstart', ['$event'])
|
||||||
|
public onDocumentTouch(event: TouchEvent): void {
|
||||||
|
if (event !== this._touchEvent) {
|
||||||
|
this.onOffClick.emit(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,130 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import { IMultiSelectOption } from './types';
|
||||||
|
|
||||||
|
interface StringHashMap<T> {
|
||||||
|
[k: string]: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'searchFilter'
|
||||||
|
})
|
||||||
|
export class MultiSelectSearchFilter implements PipeTransform {
|
||||||
|
|
||||||
|
private _lastOptions: IMultiSelectOption[];
|
||||||
|
private _searchCache: StringHashMap<IMultiSelectOption[]> = {};
|
||||||
|
private _searchCacheInclusive: StringHashMap<boolean | number> = {};
|
||||||
|
private _prevSkippedItems: StringHashMap<number> = {};
|
||||||
|
|
||||||
|
transform(
|
||||||
|
options: IMultiSelectOption[],
|
||||||
|
str = '',
|
||||||
|
limit = 0,
|
||||||
|
renderLimit = 0,
|
||||||
|
searchFunction: (str: string) => RegExp,
|
||||||
|
): IMultiSelectOption[] {
|
||||||
|
str = str.toLowerCase();
|
||||||
|
|
||||||
|
// Drop cache because options were updated
|
||||||
|
if (options !== this._lastOptions) {
|
||||||
|
this._lastOptions = options;
|
||||||
|
this._searchCache = {};
|
||||||
|
this._searchCacheInclusive = {};
|
||||||
|
this._prevSkippedItems = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredOpts = this._searchCache.hasOwnProperty(str)
|
||||||
|
? this._searchCache[str]
|
||||||
|
: this._doSearch(options, str, limit, searchFunction);
|
||||||
|
|
||||||
|
const isUnderLimit = options.length <= limit;
|
||||||
|
|
||||||
|
return isUnderLimit
|
||||||
|
? filteredOpts
|
||||||
|
: this._limitRenderedItems(filteredOpts, renderLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getSubsetOptions(
|
||||||
|
options: IMultiSelectOption[],
|
||||||
|
prevOptions: IMultiSelectOption[],
|
||||||
|
prevSearchStr: string
|
||||||
|
) {
|
||||||
|
const prevInclusiveOrIdx = this._searchCacheInclusive[prevSearchStr];
|
||||||
|
|
||||||
|
if (prevInclusiveOrIdx === true) {
|
||||||
|
// If have previous results and it was inclusive, do only subsearch
|
||||||
|
return prevOptions;
|
||||||
|
} else if (typeof prevInclusiveOrIdx === 'number') {
|
||||||
|
// Or reuse prev results with unchecked ones
|
||||||
|
return [...prevOptions, ...options.slice(prevInclusiveOrIdx)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _doSearch(options: IMultiSelectOption[], str: string, limit: number, searchFunction: (str: string) => RegExp) {
|
||||||
|
const prevStr = str.slice(0, -1);
|
||||||
|
const prevResults = this._searchCache[prevStr];
|
||||||
|
const prevResultShift = this._prevSkippedItems[prevStr] || 0;
|
||||||
|
|
||||||
|
if (prevResults) {
|
||||||
|
options = this._getSubsetOptions(options, prevResults, prevStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const optsLength = options.length;
|
||||||
|
const maxFound = limit > 0 ? Math.min(limit, optsLength) : optsLength;
|
||||||
|
const regexp = searchFunction(str);
|
||||||
|
const filteredOpts: IMultiSelectOption[] = [];
|
||||||
|
|
||||||
|
let i = 0, founded = 0, removedFromPrevResult = 0;
|
||||||
|
|
||||||
|
const doesOptionMatch = (option: IMultiSelectOption) => regexp.test(option.name);
|
||||||
|
const getChildren = (option: IMultiSelectOption) =>
|
||||||
|
options.filter(child => child.parentId === option.id);
|
||||||
|
const getParent = (option: IMultiSelectOption) =>
|
||||||
|
options.find(parent => option.parentId === parent.id);
|
||||||
|
const foundFn = (item: any) => { filteredOpts.push(item); founded++; };
|
||||||
|
const notFoundFn = prevResults ? () => removedFromPrevResult++ : () => { };
|
||||||
|
|
||||||
|
for (; i < optsLength && founded < maxFound; ++i) {
|
||||||
|
const option = options[i];
|
||||||
|
const directMatch = doesOptionMatch(option);
|
||||||
|
|
||||||
|
if (directMatch) {
|
||||||
|
foundFn(option);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof option.parentId === 'undefined') {
|
||||||
|
const childrenMatch = getChildren(option).some(doesOptionMatch);
|
||||||
|
|
||||||
|
if (childrenMatch) {
|
||||||
|
foundFn(option);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof option.parentId !== 'undefined') {
|
||||||
|
const parentMatch = doesOptionMatch(getParent(option));
|
||||||
|
|
||||||
|
if (parentMatch) {
|
||||||
|
foundFn(option);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notFoundFn();
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalIterations = i + prevResultShift;
|
||||||
|
|
||||||
|
this._searchCache[str] = filteredOpts;
|
||||||
|
this._searchCacheInclusive[str] = i === optsLength || totalIterations;
|
||||||
|
this._prevSkippedItems[str] = removedFromPrevResult + prevResultShift;
|
||||||
|
|
||||||
|
return filteredOpts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _limitRenderedItems<T>(items: T[], limit: number): T[] {
|
||||||
|
return items.length > limit && limit > 0 ? items.slice(0, limit) : items;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
export interface IMultiSelectOption {
|
||||||
|
id: any;
|
||||||
|
name: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
isLabel?: boolean;
|
||||||
|
parentId?: any;
|
||||||
|
params?: any;
|
||||||
|
classes?: string;
|
||||||
|
image?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMultiSelectSettings {
|
||||||
|
pullRight?: boolean;
|
||||||
|
enableSearch?: boolean;
|
||||||
|
closeOnClickOutside?: boolean;
|
||||||
|
/**
|
||||||
|
* 0 - By default
|
||||||
|
* If `enableSearch=true` and total amount of items more then `searchRenderLimit` (0 - No limit)
|
||||||
|
* then render items only when user typed more then or equal `searchRenderAfter` charachters
|
||||||
|
*/
|
||||||
|
searchRenderLimit?: number;
|
||||||
|
/**
|
||||||
|
* 3 - By default
|
||||||
|
*/
|
||||||
|
searchRenderAfter?: number;
|
||||||
|
/**
|
||||||
|
* 0 - By default
|
||||||
|
* If >0 will render only N first items
|
||||||
|
*/
|
||||||
|
searchMaxLimit?: number;
|
||||||
|
/**
|
||||||
|
* 0 - By default
|
||||||
|
* Used with searchMaxLimit to further limit rendering for optimization
|
||||||
|
* Should be less than searchMaxLimit to take effect
|
||||||
|
*/
|
||||||
|
searchMaxRenderedItems?: number;
|
||||||
|
checkedStyle?: 'checkboxes' | 'glyphicon' | 'fontawesome' | 'visual';
|
||||||
|
buttonClasses?: string;
|
||||||
|
itemClasses?: string;
|
||||||
|
containerClasses?: string;
|
||||||
|
selectionLimit?: number;
|
||||||
|
minSelectionLimit?: number;
|
||||||
|
closeOnSelect?: boolean;
|
||||||
|
autoUnselect?: boolean;
|
||||||
|
showCheckAll?: boolean;
|
||||||
|
showUncheckAll?: boolean;
|
||||||
|
fixedTitle?: boolean;
|
||||||
|
dynamicTitleMaxItems?: number;
|
||||||
|
maxHeight?: string;
|
||||||
|
displayAllSelectedText?: boolean;
|
||||||
|
isLazyLoad?: boolean;
|
||||||
|
loadViewDistance?: number;
|
||||||
|
stopScrollPropagation?: boolean;
|
||||||
|
selectAddedValues?: boolean;
|
||||||
|
/**
|
||||||
|
* false - By default
|
||||||
|
* If activated label IDs don't count and won't be written to the model.
|
||||||
|
*/
|
||||||
|
ignoreLabels?: boolean;
|
||||||
|
/**
|
||||||
|
* false - By default
|
||||||
|
* If activated, the title will show selections in the order they were selected.
|
||||||
|
*/
|
||||||
|
maintainSelectionOrderInTitle?: boolean;
|
||||||
|
/**
|
||||||
|
* @default true
|
||||||
|
* Set the focus back to the input control when the dropdown closed
|
||||||
|
*/
|
||||||
|
focusBack?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMultiSelectTexts {
|
||||||
|
checkAll?: string;
|
||||||
|
uncheckAll?: string;
|
||||||
|
checked?: string;
|
||||||
|
checkedPlural?: string;
|
||||||
|
searchPlaceholder?: string;
|
||||||
|
searchEmptyResult?: string;
|
||||||
|
searchNoRenderText?: string;
|
||||||
|
defaultTitle?: string;
|
||||||
|
allSelected?: string;
|
||||||
|
}
|
@ -1295,18 +1295,6 @@ if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
|
|||||||
echo "[*] Installing Bitcoin Mainnet electrs start script"
|
echo "[*] Installing Bitcoin Mainnet electrs start script"
|
||||||
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-mainnet" "${BITCOIN_ELECTRS_HOME}"
|
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-mainnet" "${BITCOIN_ELECTRS_HOME}"
|
||||||
|
|
||||||
echo "[*] Installing Bitcoin crontab"
|
|
||||||
case $OS in
|
|
||||||
FreeBSD)
|
|
||||||
echo [*] FIXME: must only crontab enabled daemons
|
|
||||||
osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.crontab"
|
|
||||||
osSudo "${ROOT_USER}" crontab -u "${MINFEE_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/minfee.crontab"
|
|
||||||
;;
|
|
||||||
Debian)
|
|
||||||
(crontab -l ; echo "@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "[*] Configuring Bitcoin Mainnet RPC credentials in electrs start script"
|
echo "[*] Configuring Bitcoin Mainnet RPC credentials in electrs start script"
|
||||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
|
||||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
|
||||||
@ -1321,13 +1309,6 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
|||||||
echo "[*] Installing Bitcoin Testnet electrs start script"
|
echo "[*] Installing Bitcoin Testnet electrs start script"
|
||||||
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-testnet" "${BITCOIN_ELECTRS_HOME}"
|
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-testnet" "${BITCOIN_ELECTRS_HOME}"
|
||||||
|
|
||||||
case $OS in
|
|
||||||
Debian)
|
|
||||||
echo "[*] Installing Bitcoin-testnet crontab"
|
|
||||||
(crontab -l ; echo "@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/electrs-start-testnet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "[*] Configuring Bitcoin Testnet RPC credentials in electrs start script"
|
echo "[*] Configuring Bitcoin Testnet RPC credentials in electrs start script"
|
||||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
|
||||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
|
||||||
@ -1342,13 +1323,6 @@ if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
|||||||
echo "[*] Installing Bitcoin Signet electrs start script"
|
echo "[*] Installing Bitcoin Signet electrs start script"
|
||||||
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-signet" "${BITCOIN_ELECTRS_HOME}"
|
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-signet" "${BITCOIN_ELECTRS_HOME}"
|
||||||
|
|
||||||
case $OS in
|
|
||||||
Debian)
|
|
||||||
echo "[*] Installing Bitcoin-signet crontab"
|
|
||||||
(crontab -l ; echo "@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/electrs-start-signet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "[*] Configuring Bitcoin Signet RPC credentials in electrs start script"
|
echo "[*] Configuring Bitcoin Signet RPC credentials in electrs start script"
|
||||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
|
||||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
|
||||||
@ -1369,9 +1343,6 @@ if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
|
|||||||
echo [*] FIXME: must only crontab enabled daemons
|
echo [*] FIXME: must only crontab enabled daemons
|
||||||
osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/elements.crontab"
|
osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/elements.crontab"
|
||||||
;;
|
;;
|
||||||
Debian)
|
|
||||||
(crontab -l ; echo "6 * * * * cd $HOME/asset_registry_db && git pull origin master >/dev/null 2>&1") | osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" -
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
echo "[*] Configuring Elements Liquid RPC credentials in electrs start script"
|
echo "[*] Configuring Elements Liquid RPC credentials in electrs start script"
|
||||||
@ -1388,13 +1359,6 @@ if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
|
|||||||
echo "[*] Installing Elements Liquid Testnet electrs start script"
|
echo "[*] Installing Elements Liquid Testnet electrs start script"
|
||||||
osSudo "${ROOT_USER}" install -c -o "${ELEMENTS_USER}" -g "${ELEMENTS_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-liquidtestnet" "${ELEMENTS_ELECTRS_HOME}"
|
osSudo "${ROOT_USER}" install -c -o "${ELEMENTS_USER}" -g "${ELEMENTS_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-liquidtestnet" "${ELEMENTS_ELECTRS_HOME}"
|
||||||
|
|
||||||
case $OS in
|
|
||||||
Debian)
|
|
||||||
echo "[*] Installing Elements-testnet crontab"
|
|
||||||
(crontab -l ; echo "6 * * * * cd $HOME/asset_registry_testnet_db && git pull origin master >/dev/null 2>&1") | osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" -
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "[*] Installing Elements Liquid Testnet RPC credentials"
|
echo "[*] Installing Elements Liquid Testnet RPC credentials"
|
||||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${ELEMENTS_HOME}/elements.conf"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${ELEMENTS_HOME}/elements.conf"
|
||||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${ELEMENTS_HOME}/elements.conf"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${ELEMENTS_HOME}/elements.conf"
|
||||||
@ -1407,6 +1371,45 @@ if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
|
|||||||
osSudo "${ROOT_USER}" sed -i.orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquidtestnet"
|
osSudo "${ROOT_USER}" sed -i.orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquidtestnet"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
################################
|
||||||
|
# Install all Electrs Cronjobs #
|
||||||
|
################################
|
||||||
|
echo "[*] Installing crontabs"
|
||||||
|
case $OS in
|
||||||
|
FreeBSD)
|
||||||
|
echo [*] FIXME: must only crontab enabled daemons
|
||||||
|
osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.crontab"
|
||||||
|
osSudo "${ROOT_USER}" crontab -u "${MINFEE_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/minfee.crontab"
|
||||||
|
;;
|
||||||
|
Debian)
|
||||||
|
crontab_bitcoin=()
|
||||||
|
if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
|
||||||
|
echo [*] Installing Electrs Mainnet Cronjob
|
||||||
|
crontab_bitcoin+="@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet\n"
|
||||||
|
fi
|
||||||
|
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||||
|
echo [*] Installing Electrs Testnet Cronjob
|
||||||
|
crontab_bitcoin+="@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/electrs-start-testnet\n"
|
||||||
|
fi
|
||||||
|
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
|
echo [*] Installing Electrs Signet Cronjob
|
||||||
|
crontab_bitcoin+="@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/electrs-start-signet\n"
|
||||||
|
fi
|
||||||
|
echo "${crontab_bitcoin}" | crontab -u "${BITCOIN_USER}" -
|
||||||
|
|
||||||
|
crontab_elements=()
|
||||||
|
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
|
||||||
|
echo [*] Installing Liquid Asset Mainnet Cronjob
|
||||||
|
crontab_elements+="6 * * * * cd $HOME/asset_registry_db && git pull origin master >/dev/null 2>&1\n"
|
||||||
|
fi
|
||||||
|
if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
|
||||||
|
echo [*] Installing Liquid Asset Testnet Cronjob
|
||||||
|
crontab_elements+="6 * * * * cd $HOME/asset_registry_testnet_db && git pull origin master >/dev/null 2>&1\n"
|
||||||
|
fi
|
||||||
|
echo "${crontab_elements}" | crontab -u "${ELEMENTS_USER}" -
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
# Bisq instance for Bitcoin Mainnet #
|
# Bisq instance for Bitcoin Mainnet #
|
||||||
#####################################
|
#####################################
|
||||||
@ -1553,6 +1556,29 @@ case $OS in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
##### OS set Linux user ulimits
|
||||||
|
|
||||||
|
echo "[*] Setting ulimits for users"
|
||||||
|
case $OS in
|
||||||
|
|
||||||
|
FreeBSD)
|
||||||
|
;;
|
||||||
|
|
||||||
|
Debian)
|
||||||
|
cat >> /etc/security/limits.conf <<EOF
|
||||||
|
* soft nproc 200000
|
||||||
|
* hard nproc 200000
|
||||||
|
* soft nofile 200000
|
||||||
|
* hard nofile 200000
|
||||||
|
EOF
|
||||||
|
echo "session required pam_limits.so" >> /etc/pam.d/common-session
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### OS services
|
##### OS services
|
||||||
|
|
||||||
#if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
|
#if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
|
||||||
@ -1628,6 +1654,8 @@ esac
|
|||||||
|
|
||||||
##### finish
|
##### finish
|
||||||
|
|
||||||
|
echo 'Please reboot to start all the services.'
|
||||||
|
|
||||||
echo '[*] Done!'
|
echo '[*] Done!'
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user