mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
update display obj on union change (#2224)
* update display obj on union change * deelete unnecessary changes * more efficient * fix: properly change height of form object * more config examples --------- Co-authored-by: waterplea <alexander@inkin.ru>
This commit is contained in:
@@ -22,8 +22,7 @@
|
||||
[formControlName]="entry.key"
|
||||
(ionFocus)="presentAlertChangeWarning(entry.key, spec)"
|
||||
(ionChange)="handleInputChange()"
|
||||
>
|
||||
</ion-textarea>
|
||||
></ion-textarea>
|
||||
<ng-template #notTextArea>
|
||||
<ion-input
|
||||
type="text"
|
||||
@@ -38,8 +37,7 @@
|
||||
[formControlName]="entry.key"
|
||||
(ionFocus)="presentAlertChangeWarning(entry.key, spec)"
|
||||
(ionChange)="handleInputChange()"
|
||||
>
|
||||
</ion-input>
|
||||
></ion-input>
|
||||
</ng-template>
|
||||
<ion-button
|
||||
*ngIf="spec.type === 'string' && spec.masked"
|
||||
@@ -59,8 +57,9 @@
|
||||
slot="end"
|
||||
color="light"
|
||||
style="font-size: medium"
|
||||
>{{ spec.units }}</ion-note
|
||||
>
|
||||
{{ spec.units }}
|
||||
</ion-note>
|
||||
</ion-item>
|
||||
<p class="error-message">
|
||||
<span *ngIf="(formGroup | getControl: entry.key).errors as errors">
|
||||
@@ -92,11 +91,11 @@
|
||||
*ngIf="original?.[entry.key] === undefined"
|
||||
color="success"
|
||||
>
|
||||
(New)</ion-text
|
||||
>
|
||||
(New)
|
||||
</ion-text>
|
||||
<ion-text *ngIf="entry.value.dirty" color="warning">
|
||||
(Edited)</ion-text
|
||||
>
|
||||
(Edited)
|
||||
</ion-text>
|
||||
</b>
|
||||
</ion-label>
|
||||
<!-- boolean -->
|
||||
@@ -157,14 +156,9 @@
|
||||
></ion-icon>
|
||||
</ion-item-divider>
|
||||
<!-- body -->
|
||||
<div
|
||||
<tui-expand
|
||||
[expanded]="objectDisplay[entry.key].expanded"
|
||||
[id]="objectId | toElementId: entry.key"
|
||||
[ngStyle]="{
|
||||
'max-height': objectDisplay[entry.key].height,
|
||||
overflow: 'hidden',
|
||||
'transition-property': 'max-height',
|
||||
'transition-duration': '.42s'
|
||||
}"
|
||||
>
|
||||
<div class="nested-wrapper">
|
||||
<form-object
|
||||
@@ -172,11 +166,10 @@
|
||||
[formGroup]="$any(entry.value)"
|
||||
[current]="current?.[entry.key]"
|
||||
[original]="original?.[entry.key]"
|
||||
(onResize)="resize(entry.key)"
|
||||
(hasNewOptions)="setHasNew(entry.key)"
|
||||
></form-object>
|
||||
</div>
|
||||
</div>
|
||||
</tui-expand>
|
||||
</ng-container>
|
||||
<!-- union -->
|
||||
<form-union
|
||||
@@ -259,15 +252,10 @@
|
||||
></ion-icon>
|
||||
</ion-item>
|
||||
<!-- object/union body -->
|
||||
<div
|
||||
<tui-expand
|
||||
style="padding-left: 24px"
|
||||
[expanded]="objectListDisplay[entry.key][i].expanded"
|
||||
[id]="objectId | toElementId: entry.key:i"
|
||||
[ngStyle]="{
|
||||
'max-height': objectListDisplay[entry.key][i].height,
|
||||
overflow: 'hidden',
|
||||
'transition-property': 'max-height',
|
||||
'transition-duration': '.42s'
|
||||
}"
|
||||
>
|
||||
<form-object
|
||||
*ngIf="spec.subtype === 'object'"
|
||||
@@ -278,7 +266,6 @@
|
||||
(onInputChange)="
|
||||
updateLabel(entry.key, i, $any(spec.spec)['display-as'])
|
||||
"
|
||||
(onResize)="resize(entry.key, i)"
|
||||
></form-object>
|
||||
<form-union
|
||||
*ngIf="spec.subtype === 'union'"
|
||||
@@ -289,7 +276,6 @@
|
||||
(onInputChange)="
|
||||
updateLabel(entry.key, i, $any(spec.spec)['display-as'])
|
||||
"
|
||||
(onResize)="resize(entry.key, i)"
|
||||
></form-union>
|
||||
<div style="text-align: right; padding-top: 12px">
|
||||
<ion-button
|
||||
@@ -301,7 +287,7 @@
|
||||
Delete
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
</tui-expand>
|
||||
</ng-container>
|
||||
<!-- string or number -->
|
||||
<div
|
||||
@@ -318,8 +304,7 @@
|
||||
$any(spec.spec).placeholder || 'Enter ' + spec.name
|
||||
"
|
||||
[formControlName]="i"
|
||||
>
|
||||
</ion-input>
|
||||
></ion-input>
|
||||
<ion-button
|
||||
strong
|
||||
fill="clear"
|
||||
|
||||
@@ -18,6 +18,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
import { TuiElasticContainerModule } from '@taiga-ui/kit'
|
||||
import { EnumListPageModule } from 'src/app/modals/enum-list/enum-list.module'
|
||||
import { TuiExpandModule } from '@taiga-ui/core'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -39,6 +40,7 @@ import { EnumListPageModule } from 'src/app/modals/enum-list/enum-list.module'
|
||||
SharedPipesModule,
|
||||
EnumListPageModule,
|
||||
TuiElasticContainerModule,
|
||||
TuiExpandModule,
|
||||
],
|
||||
exports: [FormObjectComponent, FormLabelComponent],
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
Inject,
|
||||
inject,
|
||||
SimpleChanges,
|
||||
} from '@angular/core'
|
||||
import { FormArray, UntypedFormArray, UntypedFormGroup } from '@angular/forms'
|
||||
import { AlertButton, AlertController, ModalController } from '@ionic/angular'
|
||||
@@ -41,15 +42,14 @@ export class FormObjectComponent {
|
||||
@Input() current?: Config
|
||||
@Input() original?: Config
|
||||
@Output() onInputChange = new EventEmitter<void>()
|
||||
@Output() onResize = new EventEmitter<void>()
|
||||
@Output() hasNewOptions = new EventEmitter<void>()
|
||||
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<T extends ValueSpec>(
|
||||
@@ -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<void>()
|
||||
|
||||
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({
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
[formGroup]="formGroup"
|
||||
[current]="current"
|
||||
[original]="original"
|
||||
(onResize)="resize()"
|
||||
></form-object>
|
||||
</tui-elastic-container>
|
||||
</div>
|
||||
|
||||
@@ -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:
|
||||
'<p>The Bitcoin Core node to connect to over the peer-to-peer (P2P) interface:</p><ul><li><strong>Bitcoin Core</strong>: The Bitcoin Core service installed on this device</li><li><strong>External Node</strong>: A Bitcoin node running on a different device</li></ul>',
|
||||
'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,
|
||||
|
||||
@@ -21,13 +21,17 @@ export class ThemeSwitcherService extends BehaviorSubject<string> {
|
||||
.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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user