diff --git a/frontend/projects/ui/src/app/components/form-object/form-object.component.html b/frontend/projects/ui/src/app/components/form-object/form-object.component.html index 4332f112b..8bbfc5c78 100644 --- a/frontend/projects/ui/src/app/components/form-object/form-object.component.html +++ b/frontend/projects/ui/src/app/components/form-object/form-object.component.html @@ -22,8 +22,7 @@ [formControlName]="entry.key" (ionFocus)="presentAlertChangeWarning(entry.key, spec)" (ionChange)="handleInputChange()" - > - + > - + > {{ spec.units }} + {{ spec.units }} +

@@ -92,11 +91,11 @@ *ngIf="original?.[entry.key] === undefined" color="success" > - (New) + (New) + - (Edited) + (Edited) + @@ -157,14 +156,9 @@ > -

-
+ -
-
+
- + > () - @Output() onResize = new EventEmitter() @Output() hasNewOptions = new EventEmitter() warningAck: { [key: string]: boolean } = {} unmasked: { [key: string]: boolean } = {} objectDisplay: { - [key: string]: { expanded: boolean; height: string; hasNewOptions: boolean } + [key: string]: { expanded: boolean; hasNewOptions: boolean } } = {} objectListDisplay: { - [key: string]: { expanded: boolean; height: string; displayAs: string }[] + [key: string]: { expanded: boolean; displayAs: string }[] } = {} objectId = v4() @@ -63,6 +63,37 @@ export class FormObjectComponent { ) {} ngOnInit() { + this.setDisplays() + + // setTimeout hack to avoid ExpressionChangedAfterItHasBeenCheckedError + setTimeout(() => { + if ( + this.original && + Object.keys(this.current || {}).some( + key => this.original![key] === undefined, + ) + ) + this.hasNewOptions.emit() + }) + } + + ngOnChanges(changes: SimpleChanges) { + const specChanges = changes['objectSpec'] + + if (!specChanges) return + + if ( + !specChanges.firstChange && + Object.keys({ + ...specChanges.previousValue, + ...specChanges.currentValue, + }).length !== Object.keys(specChanges.previousValue).length + ) { + this.setDisplays() + } + } + + private setDisplays() { Object.keys(this.objectSpec).forEach(key => { const spec = this.objectSpec[key] @@ -74,7 +105,6 @@ export class FormObjectComponent { ] this.objectListDisplay[key][index] = { expanded: false, - height: '0px', displayAs: displayAs ? (Mustache as any).render(displayAs, obj) : '', @@ -83,33 +113,10 @@ export class FormObjectComponent { } else if (spec.type === 'object') { this.objectDisplay[key] = { expanded: false, - height: '0px', hasNewOptions: false, } } }) - - // setTimeout hack to avoid ExpressionChangedAfterItHasBeenCheckedError - setTimeout(() => { - if (this.original) { - Object.keys(this.current || {}).forEach(key => { - if ((this.original as Config)[key] === undefined) { - this.hasNewOptions.emit() - } - }) - } - }, 10) - } - - resize(key: string, i?: number): void { - setTimeout(() => { - if (i !== undefined) { - this.objectListDisplay[key][i].height = this.getScrollHeight(key, i) - } else { - this.objectDisplay[key].height = this.getScrollHeight(key) - } - this.onResize.emit() - }, 250) // 250 to match transition-duration defined in html, for smooth recursive resize } addListItemWrapper( @@ -121,20 +128,11 @@ export class FormObjectComponent { toggleExpandObject(key: string) { this.objectDisplay[key].expanded = !this.objectDisplay[key].expanded - this.objectDisplay[key].height = this.objectDisplay[key].expanded - ? this.getScrollHeight(key) - : '0px' - this.onResize.emit() } toggleExpandListObject(key: string, i: number) { this.objectListDisplay[key][i].expanded = !this.objectListDisplay[key][i].expanded - this.objectListDisplay[key][i].height = this.objectListDisplay[key][i] - .expanded - ? this.getScrollHeight(key, i) - : '0px' - this.onResize.emit() } updateLabel(key: string, i: number, displayAs: string) { @@ -275,7 +273,6 @@ export class FormObjectComponent { 'display-as' ] this.objectListDisplay[key].push({ - height: '0px', expanded: false, displayAs: displayAs ? Mustache.render(displayAs, newItem.value) : '', }) @@ -296,8 +293,8 @@ export class FormObjectComponent { } private deleteListItem(key: string, index: number, markDirty = true): void { - if (this.objectListDisplay[key]) - this.objectListDisplay[key][index].height = '0px' + // if (this.objectListDisplay[key]) + // this.objectListDisplay[key][index].height = '0px' const arr = this.formGroup.get(key) as UntypedFormArray if (markDirty) arr.markAsDirty() pauseFor(250).then(() => { @@ -328,13 +325,6 @@ export class FormObjectComponent { arr.markAsDirty() } - private getScrollHeight(key: string, index = 0): string { - const element = this.document.getElementById( - getElementId(this.objectId, key, index), - ) - return `${element?.scrollHeight}px` - } - asIsOrder() { return 0 } @@ -352,8 +342,6 @@ export class FormUnionComponent { @Input() current?: Config @Input() original?: Config - @Output() onResize = new EventEmitter() - get unionValue() { return this.formGroup.get(this.spec.tag.id)?.value } @@ -374,10 +362,7 @@ export class FormUnionComponent { objectId = v4() - constructor( - private readonly formService: FormService, - @Inject(DOCUMENT) private readonly document: Document, - ) {} + constructor(private readonly formService: FormService) {} updateUnion(e: any): void { const tagId = this.spec.tag.id @@ -397,12 +382,6 @@ export class FormUnionComponent { this.formGroup.addControl(control, unionGroup.controls[control]) }) } - - resize(): void { - setTimeout(() => { - this.onResize.emit() - }, 250) // 250 to match transition-duration, defined in html - } } @Component({ diff --git a/frontend/projects/ui/src/app/components/form-object/form-union.component.html b/frontend/projects/ui/src/app/components/form-object/form-union.component.html index 8dcd5bcd0..ed9fc31be 100644 --- a/frontend/projects/ui/src/app/components/form-object/form-union.component.html +++ b/frontend/projects/ui/src/app/components/form-object/form-union.component.html @@ -37,7 +37,6 @@ [formGroup]="formGroup" [current]="current" [original]="original" - (onResize)="resize()" >
diff --git a/frontend/projects/ui/src/app/services/api/api.fixures.ts b/frontend/projects/ui/src/app/services/api/api.fixures.ts index 6530cb5a2..0c65b2597 100644 --- a/frontend/projects/ui/src/app/services/api/api.fixures.ts +++ b/frontend/projects/ui/src/app/services/api/api.fixures.ts @@ -1169,6 +1169,113 @@ export module Mock { } as any // @TODO why is this necessary? export const ConfigSpec: RR.GetPackageConfigRes['spec'] = { + bitcoin: { + type: 'object', + name: 'Bitcoin Settings', + description: + 'RPC and P2P interface configuration options for Bitcoin Core', + spec: { + 'bitcoind-p2p': { + type: 'union', + tag: { + id: 'type', + name: 'Bitcoin Core P2P', + description: + '

The Bitcoin Core node to connect to over the peer-to-peer (P2P) interface:

', + 'variant-names': { + internal: 'Bitcoin Core', + external: 'External Node', + }, + }, + default: 'internal', + variants: { + internal: {}, + external: { + 'p2p-host': { + type: 'string', + name: 'Public Address', + description: 'The public address of your Bitcoin Core server', + nullable: false, + masked: false, + copyable: false, + }, + 'p2p-port': { + type: 'number', + name: 'P2P Port', + description: + 'The port that your Bitcoin Core P2P server is bound to', + nullable: false, + range: '[0,65535]', + integral: true, + default: 8333, + }, + }, + }, + }, + }, + }, + advanced: { + name: 'Advanced', + type: 'object', + description: 'Advanced settings', + spec: { + rpcsettings: { + name: 'RPC Settings', + type: 'object', + description: 'rpc username and password', + warning: + 'Adding RPC users gives them special permissions on your node.', + spec: { + rpcuser2: { + name: 'RPC Username', + type: 'string', + description: 'rpc username', + nullable: false, + default: 'defaultrpcusername', + pattern: '^[a-zA-Z]+$', + 'pattern-description': 'must contain only letters.', + masked: false, + copyable: true, + }, + rpcuser: { + name: 'RPC Username', + type: 'string', + description: 'rpc username', + nullable: false, + default: 'defaultrpcusername', + pattern: '^[a-zA-Z]+$', + 'pattern-description': 'must contain only letters.', + masked: false, + copyable: true, + }, + rpcpass: { + name: 'RPC User Password', + type: 'string', + description: 'rpc password', + nullable: false, + default: { + charset: 'a-z,A-Z,2-9', + len: 20, + }, + masked: true, + copyable: true, + }, + rpcpass2: { + name: 'RPC User Password', + type: 'string', + description: 'rpc password', + nullable: false, + default: { + charset: 'a-z,A-Z,2-9', + len: 20, + }, + masked: true, + copyable: true, + }, + }, + }, + }, + }, testnet: { name: 'Testnet', type: 'boolean', @@ -1449,6 +1556,29 @@ export module Mock { }, }, external: { + 'emergency-contact': { + name: 'Emergency Contact', + type: 'object', + description: 'The person to contact in case of emergency.', + spec: { + name: { + type: 'string', + name: 'Name', + nullable: false, + masked: false, + copyable: false, + pattern: '^[a-zA-Z]+$', + 'pattern-description': 'Must contain only letters.', + }, + email: { + type: 'string', + name: 'Email', + nullable: false, + masked: false, + copyable: true, + }, + }, + }, 'public-domain': { name: 'Public Domain', type: 'string', @@ -1520,8 +1650,8 @@ export module Mock { copyable: false, }, }, - advanced: { - name: 'Advanced', + 'more-advanced': { + name: 'More Advanced', type: 'object', description: 'Advanced settings', spec: { @@ -1726,8 +1856,7 @@ export module Mock { rulemakers: [], }, 'bitcoin-node': { - type: 'external', - 'public-domain': 'hello.com', + type: 'internal', }, port: 20, rpcallowip: undefined, diff --git a/frontend/projects/ui/src/app/services/theme-switcher.service.ts b/frontend/projects/ui/src/app/services/theme-switcher.service.ts index 9c93283ab..eac9c5425 100644 --- a/frontend/projects/ui/src/app/services/theme-switcher.service.ts +++ b/frontend/projects/ui/src/app/services/theme-switcher.service.ts @@ -21,13 +21,17 @@ export class ThemeSwitcherService extends BehaviorSubject { .watch$('ui', 'theme') .pipe(take(1), filter(Boolean)) .subscribe(theme => { - this.next(theme) + this.updateTheme(theme) }) } - override next(currentTheme: string): void { - this.embassyApi.setDbValue(['theme'], currentTheme) - this.windowRef.document.body.setAttribute('data-theme', currentTheme) - super.next(currentTheme) + override next(theme: string): void { + this.embassyApi.setDbValue(['theme'], theme) + this.updateTheme(theme) + } + + private updateTheme(theme: string): void { + this.windowRef.document.body.setAttribute('data-theme', theme) + super.next(theme) } }