switch to warning, general cleaning up tech debt

This commit is contained in:
Matt Hill
2021-08-20 07:51:56 -06:00
committed by Aiden McClelland
parent 2e6513ed03
commit f0d331b222
17 changed files with 93 additions and 355 deletions

View File

@@ -1,30 +0,0 @@
<ion-item-group>
<!-- error -->
<ng-container *ngIf="error">
<ion-item>
<ion-icon slot="start" name="warning-outline" color="danger" size="small"></ion-icon>
<ion-label>
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
</ion-label>
</ion-item>
<ion-item-divider *ngIf="spec.description || spec['change-warning']"></ion-item-divider>
</ng-container>
<!-- description -->
<ion-item *ngIf="spec.description">
<ion-label>
<p>
<ion-text color="dark">Description</ion-text>
</p>
<p [innerHTML]="spec.description | markdown"></p>
</ion-label>
</ion-item>
<!-- warning -->
<ion-item *ngIf="spec['change-warning']">
<ion-label>
<p>
<ion-text color="warning">Warning!</ion-text>
</p>
<p [innerHTML]="spec['change-warning'] | markdown"></p>
</ion-label>
</ion-item>
</ion-item-group>

View File

@@ -1,20 +0,0 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { ConfigHeaderComponent } from './config-header.component'
import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module'
@NgModule({
declarations: [
ConfigHeaderComponent,
],
imports: [
CommonModule,
IonicModule,
RouterModule.forChild([]),
SharingModule,
],
exports: [ConfigHeaderComponent],
})
export class ConfigHeaderComponentModule { }

View File

@@ -1,12 +0,0 @@
import { Component, Input } from '@angular/core'
import { ValueSpec } from 'src/app/pkg-config/config-types'
@Component({
selector: 'config-header',
templateUrl: './config-header.component.html',
styleUrls: ['./config-header.component.scss'],
})
export class ConfigHeaderComponent {
@Input() spec: ValueSpec
@Input() error: string
}

View File

@@ -35,14 +35,14 @@
<ion-note *ngIf="spec.units" slot="end" color="light" style="font-size: medium;">{{ spec.units }}</ion-note>
</ion-item>
<!-- boolean -->
<ion-item color="dark" *ngIf="spec.type === 'boolean'">
<ion-item *ngIf="spec.type === 'boolean'">
<ion-label>{{ spec.name }}</ion-label>
<ion-toggle style="--background: var(--ion-color-step-600);" slot="end" color="light" [formControlName]="entry.key" (ionChange)="handleBooleanChange(entry.key, spec)"></ion-toggle>
<ion-toggle slot="end" [formControlName]="entry.key" (ionChange)="handleBooleanChange(entry.key, spec)"></ion-toggle>
</ion-item>
<!-- enum -->
<ion-item color="dark" *ngIf="spec.type === 'enum'">
<ion-item *ngIf="spec.type === 'enum'">
<ion-label>{{ spec.name }}</ion-label>
<ion-select [interfaceOptions]="{ message: getWarningText(spec['change-warning']) }" slot="end" placeholder="Select" [formControlName]="entry.key" [selectedText]="spec['value-names'][formGroup.get(entry.key).value]" (ionChange)="handleInputChange(entry.key, spec)">
<ion-select [interfaceOptions]="{ message: getWarningText(spec.warning) }" slot="end" placeholder="Select" [formControlName]="entry.key" [selectedText]="spec['value-names'][formGroup.get(entry.key).value]" (ionChange)="handleInputChange(entry.key, spec)">
<ion-select-option *ngFor="let option of spec.values" [value]="option">
{{ spec['value-names'][option] }}
</ion-select-option>
@@ -96,7 +96,7 @@
<!-- nested -->
<ng-container *ngIf="spec.subtype === 'object' || spec.subtype === 'union'">
<!-- nested label -->
<ion-item-divider (click)="toggleExpand(entry.key, i)" style="cursor: pointer;">
<ion-item button (click)="toggleExpand(entry.key, i)">
<form-label [data]="{
spec: {
name: objectListInfo[entry.key][i].displayAs || 'Entry ' + (i + 1)
@@ -112,7 +112,7 @@
'transition': 'transform 0.4s ease-out'
}"
></ion-icon>
</ion-item-divider>
</ion-item>
<!-- nested body -->
<div
[id]="entry.key"

View File

@@ -117,7 +117,7 @@ export class FormObjectComponent {
}
getWarningText (text: string): IonicSafeString {
return new IonicSafeString(`<ion-text color="warning">${text}</ion-text>`)
if (text) return new IonicSafeString(`<ion-text color="warning">${text}</ion-text>`)
}
handleInputChange (spec: ValueSpec) {
@@ -127,7 +127,7 @@ export class FormObjectComponent {
}
handleBooleanChange (key: string, spec: ValueSpecBoolean) {
if (spec['change-warning']) {
if (spec.warning) {
const current = this.formGroup.get(key).value
const cancelFn = () => this.formGroup.get(key).setValue(!current)
this.presentAlertChangeWarning(key, spec, undefined, cancelFn)
@@ -154,12 +154,12 @@ export class FormObjectComponent {
}
async presentAlertChangeWarning (key: string, spec: ValueSpec, okFn?: Function, cancelFn?: Function) {
if (!spec['change-warning'] || this.warningAck[key]) return okFn ? okFn() : null
if (!spec.warning || this.warningAck[key]) return okFn ? okFn() : null
this.warningAck[key] = true
const buttons = [
{
text: 'Proceed',
text: 'Ok',
handler: () => {
if (okFn) okFn()
},
@@ -178,7 +178,7 @@ export class FormObjectComponent {
const alert = await this.alertCtrl.create({
header: 'Warning',
subHeader: `Editing ${spec.name} has consequences:`,
message: spec['change-warning'],
message: spec.warning,
buttons,
})
await alert.present()

View File

@@ -3,7 +3,6 @@ import { EmverComparesPipe, EmverSatisfiesPipe, EmverDisplayPipe } from '../pipe
import { IncludesPipe } from '../pipes/includes.pipe'
import { TypeofPipe } from '../pipes/typeof.pipe'
import { MarkdownPipe } from '../pipes/markdown.pipe'
import { AnnotationStatusPipe } from '../pipes/annotation-status.pipe'
import { TruncateCenterPipe, TruncateEndPipe } from '../pipes/truncate.pipe'
import { MaskPipe } from '../pipes/mask.pipe'
import { HasUiPipe, LaunchablePipe } from '../pipes/ui.pipe'
@@ -21,7 +20,6 @@ import { PwaBackComponentModule } from '../components/pwa-back-button/pwa-back.c
IncludesPipe,
InstallState,
MarkdownPipe,
AnnotationStatusPipe,
TruncateCenterPipe,
TruncateEndPipe,
MaskPipe,
@@ -41,7 +39,6 @@ import { PwaBackComponentModule } from '../components/pwa-back-button/pwa-back.c
TypeofPipe,
IncludesPipe,
MarkdownPipe,
AnnotationStatusPipe,
TruncateEndPipe,
TruncateCenterPipe,
MaskPipe,

View File

@@ -30,17 +30,17 @@
<!-- loading -->
<ng-container *ngIf="loading">
<ion-item *ngFor="let entry of ['', '']">
<ion-button slot="start" fill="clear">
<ion-avatar slot="start" style="margin-right: 30px;">
<ion-skeleton-text animated style="width: 30px; height: 30px; border-radius: 0;"></ion-skeleton-text>
</ion-button>
</ion-avatar>
<ion-label>
<ion-skeleton-text animated style="width: 15%; height: 20px; margin-bottom: 12px;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 50%; margin-bottom: 18px;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 20%;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 100px; height: 20px; margin-bottom: 12px;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 150px; margin-bottom: 18px;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 300px;"></ion-skeleton-text>
</ion-label>
<ion-button slot="end" fill="clear">
<ion-avatar slot="end">
<ion-skeleton-text animated style="width: 80px; border-radius: 0"></ion-skeleton-text>
</ion-button>
</ion-avatar>
</ion-item>
</ng-container>

View File

@@ -8,10 +8,8 @@
</ion-header>
<ion-content class="ion-padding-top">
<text-spinner *ngIf="loading; else loaded" text="Loading Drives"></text-spinner>
<ng-template #loaded>
<ion-item-group>
<!-- always -->
<ion-item class="ion-margin-bottom">
<ion-label>
<h2>
@@ -20,26 +18,46 @@
</ion-label>
</ion-item>
<ion-item *ngIf="allPartitionsMounted">
<ion-text class="ion-text-wrap" color="warning">No partitions available. To begin a backup, insert a storage device into your Embassy.</ion-text>
</ion-item>
<!-- loading -->
<ng-container *ngIf="loading">
<ion-item-divider>
<ion-skeleton-text animated style="width: 120px; height: 16px;"></ion-skeleton-text>
</ion-item-divider>
<ion-item>
<ion-avatar slot="start" style="margin-right: 24px;">
<ion-skeleton-text animated style="width: 30px; height: 30px; border-radius: 0;"></ion-skeleton-text>
</ion-avatar>
<ion-label>
<ion-skeleton-text animated style="width: 100px; height: 20px; margin-bottom: 12px;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 50px; height: 16px; margin-bottom: 16px;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 100px;"></ion-skeleton-text>
</ion-label>
</ion-item>
</ng-container>
<ion-item-group>
<div *ngFor="let disk of disks | keyvalue">
<ion-item-divider>{{ disk.key }} - {{ disk.value.size }}</ion-item-divider>
<ion-item button *ngFor="let partition of disk.value.partitions | keyvalue" [disabled]="partition.value['is-mounted']" (click)="presentModal(partition.key)">
<ion-icon slot="start" name="save-outline" size="large"></ion-icon>
<ion-label>
<h1>{{ partition.value.label || partition.key }}</h1>
<h2>{{ partition.value.size || 'unknown size' }}</h2>
<p *ngIf="!partition.value['is-mounted']; else unavailable"><ion-text color="success">Available</ion-text></p>
<ng-template #unavailable>
<p><ion-text color="danger">Unavailable</ion-text></p>
</ng-template>
</ion-label>
</ion-item>
</div>
</ion-item-group>
</ng-template>
<!-- loaded -->
<ng-container *ngIf="!loading">
<ion-item *ngIf="allPartitionsMounted">
<ion-text class="ion-text-wrap" color="warning">No partitions available. To begin a backup, insert a storage device into your Embassy.</ion-text>
</ion-item>
<ion-item-group>
<div *ngFor="let disk of disks | keyvalue">
<ion-item-divider>{{ disk.key }} - {{ disk.value.size }}</ion-item-divider>
<ion-item button *ngFor="let partition of disk.value.partitions | keyvalue" [disabled]="partition.value['is-mounted']" (click)="presentModal(partition.key)">
<ion-icon slot="start" name="save-outline" size="large"></ion-icon>
<ion-label>
<h1>{{ partition.value.label || partition.key }}</h1>
<h2>{{ partition.value.size || 'unknown size' }}</h2>
<p *ngIf="!partition.value['is-mounted']; else unavailable"><ion-text color="success">Available</ion-text></p>
<ng-template #unavailable>
<p><ion-text color="danger">Unavailable</ion-text></p>
</ng-template>
</ion-label>
</ion-item>
</div>
</ion-item-group>
</ng-container>
</ion-item-group>
</ion-content>

View File

@@ -2,5 +2,6 @@
ion-button::part(native) {
padding-inline-start: 0;
padding-inline-end: 0;
}
};
padding-bottom: 6px;
}

View File

@@ -1,33 +0,0 @@
import { Pipe, PipeTransform } from '@angular/core'
import { Annotation } from '../pkg-config/config-utilities'
@Pipe({
name: 'annotationStatus',
})
export class AnnotationStatusPipe implements PipeTransform {
transform (a: Annotation, target: FieldStatus): boolean {
return target === getStatus(a)
}
}
function getStatus (a: Annotation): FieldStatus {
if (isInvalid(a)) return 'Invalid'
if (isEdited(a)) return 'Edited'
if (isAdded(a)) return 'Added'
return 'NoChange'
}
function isInvalid (a: Annotation): boolean {
return !!a.invalid
}
// edited only registers if its a valid edit
function isEdited (a: Annotation): boolean {
return a.edited && !a.invalid
}
function isAdded (a: Annotation): boolean {
return a.added && !a.edited && !a.invalid
}
type FieldStatus = 'Edited' | 'Added' | 'Invalid' | 'NoChange'

View File

@@ -57,7 +57,7 @@ export interface ValueSpecObject extends ListValueSpecObject, WithStandalone {
export interface WithStandalone {
name: string
description?: string
'change-warning'?: string
warning?: string
}
// no lists of booleans, lists, pointers

View File

@@ -1,19 +1,4 @@
import {
ValueSpec, ValueSpecList, DefaultString, ValueSpecUnion, ConfigSpec,
ValueSpecObject, ValueSpecString, ValueSpecEnum, ValueSpecNumber,
ValueSpecBoolean, ValueSpecPointer, ValueSpecOf, ListValueSpecType
} from './config-types'
export interface Annotation {
invalid: string | null
edited: boolean
added: boolean
}
export type Annotations<T extends string> =
T extends 'object' | 'union' ? { self: Annotation, members: { [key: string]: Annotation } } :
T extends 'list' ? { self: Annotation, members: Annotation[] } :
Annotation
import { ValueSpec, DefaultString } from './config-types'
export class Range {
min?: number
@@ -106,187 +91,6 @@ export class Range {
}
}
// converts a ValueSpecList, i.e. a spec for a list, to its inner ListValueSpec, i.e., a spec for the values within the list.
// We then augment it with the defalt values (e.g. nullable: false) to make a
export function listInnerSpec (listSpec: ValueSpecList): ValueSpecOf<ListValueSpecType> {
return {
type: listSpec.subtype,
nullable: false,
name: listSpec.name,
description: listSpec.description,
changeWarning: listSpec['change-warning'],
...listSpec.spec as any, //listSpec.spec is a ListValueSpecOf listSpec.subtype
}
}
export function mapSpecToConfigValue (spec: ValueSpec, value: any): any {
if (value === undefined) return undefined
switch (spec.type) {
case 'string': return mapStringSpec(value)
case 'number': return mapNumberSpec(value)
case 'boolean': return mapBooleanSpec(spec, value)
case 'enum': return mapEnumSpec(spec, value)
case 'list': return mapListSpec(spec, value)
case 'object': return mapObjectSpec(spec, value)
case 'union': return mapUnionSpec(spec, value)
case 'pointer': return value
}
}
export function mapConfigSpec (configSpec: ConfigSpec, value: any): object {
if (value && typeof value === 'object' && !Array.isArray(value)) {
Object.entries(configSpec).map(([key, val]) => {
value[key] = mapSpecToConfigValue(val, value[key])
if (value[key] === undefined) {
value[key] = getDefaultConfigValue(val)
}
})
return value
} else {
return getDefaultObject(configSpec)
}
}
export function mapObjectSpec (spec: ValueSpecObject, value: any): object {
if (value && typeof value === 'object' && !Array.isArray(value)) {
return mapConfigSpec(spec.spec, value)
} else {
return null
}
}
export function mapUnionSpec (spec: ValueSpecUnion, value: any): object {
if (value && typeof value === 'object' && !Array.isArray(value)) {
const variant = mapEnumSpec({
...spec.tag,
type: 'enum',
default: spec.default,
values: Object.keys(spec.variants),
'value-names': spec.tag['variant-names'],
}, value[spec.tag.id])
value = mapConfigSpec(spec.variants[variant], value)
value[spec.tag.id] = variant
return value
} else {
return getDefaultUnion(spec)
}
}
export function mapStringSpec (value: any): string {
if (typeof value === 'string') {
return value
} else {
return null
}
}
export function mapNumberSpec (value: any): number {
if (typeof value === 'number') {
return value
} else {
return null
}
}
export function mapEnumSpec (spec: ValueSpecEnum, value: any): string {
if (typeof value === 'string' && spec.values.includes(value)) {
return value
} else {
return spec.default
}
}
export function mapListSpec (spec: ValueSpecList, value: any): string[] | number[] | object[] {
if (Array.isArray(value)) {
const innerSpec = listInnerSpec(spec)
return value.map(item => mapSpecToConfigValue(innerSpec, item))
} else {
return getDefaultList(spec)
}
}
export function mapBooleanSpec (spec: ValueSpecBoolean, value: any): boolean {
if (typeof value === 'boolean') {
return value
} else {
return spec.default
}
}
export function getDefaultConfigValue (spec: ValueSpec): string | number | object | string[] | number[] | object[] | boolean | null {
switch (spec.type) {
case 'object':
return getDefaultObject(spec.spec)
case 'union':
return getDefaultUnion(spec)
case 'string':
return spec.default ? getDefaultString(spec.default) : null
case 'number':
return spec.default || null
case 'list':
return getDefaultList(spec)
case 'enum':
case 'boolean':
return spec.default
case 'pointer':
return null
}
}
export function getDefaultObject (spec: ConfigSpec): object {
const obj = { }
Object.entries(spec).map(([key, val]) => {
obj[key] = getDefaultConfigValue(val)
})
return obj
}
export function getDefaultList (spec: ValueSpecList): string[] | number[] | object[] {
if (spec.subtype === 'object') {
const l = (spec.default as any[])
const range = Range.from(spec.range)
while (l.length < range.integralMin()) {
l.push(getDefaultConfigValue(listInnerSpec(spec)))
}
return l as string[] | number[] | object[]
} else {
const l = (spec.default as any[]).map(d => getDefaultConfigValue({ ...listInnerSpec(spec), default: d }))
return l as string[] | number[] | object[]
}
}
export function getDefaultUnion (spec: ValueSpecUnion): object {
return { [spec.tag.id]: spec.default, ...getDefaultObject(spec.variants[spec.default]) }
}
export function getDefaultMapTagKey (defaultSpec: DefaultString = '', value: object): string {
const keySrc = getDefaultString(defaultSpec)
const keys = Object.keys(value)
let key = keySrc
let idx = 1
while (keys.includes(key)) {
key = `${keySrc}-${idx++}`
}
return key
}
export function getDefaultString (defaultSpec: DefaultString): string {
if (typeof defaultSpec === 'string') {
return defaultSpec
} else {
let s = ''
for (let i = 0; i < defaultSpec.len; i++) {
s = s + getRandomCharInSet(defaultSpec.charset)
}
return s
}
}
export function getDefaultDescription (spec: ValueSpec): string {
let toReturn: string | undefined
switch (spec.type) {
@@ -313,6 +117,19 @@ export function getDefaultDescription (spec: ValueSpec): string {
return toReturn || ''
}
export function getDefaultString (defaultSpec: DefaultString): string {
if (typeof defaultSpec === 'string') {
return defaultSpec
} else {
let s = ''
for (let i = 0; i < defaultSpec.len; i++) {
s = s + getRandomCharInSet(defaultSpec.charset)
}
return s
}
}
// a,g,h,A-Z,,,,-
export function getRandomCharInSet (charset: string): string {
const set = stringToCharSet(charset)

View File

@@ -962,7 +962,7 @@ export module Mock {
'name': 'Testnet',
'type': 'boolean',
'description': 'determines whether your node is running on testnet or mainnet',
'change-warning': 'Chain will have to resync!',
'warning': 'Chain will have to resync!',
'default': true,
},
'objectList': {
@@ -1020,7 +1020,7 @@ export module Mock {
'nullable': true,
'default': null,
'integral': false,
'change-warning': 'User must be at least 18.',
'warning': 'User must be at least 18.',
'range': '[18,*)',
},
},
@@ -1031,7 +1031,7 @@ export module Mock {
'type': 'list',
'subtype': 'union',
'description': 'This is a sample list of unions',
'change-warning': 'If you change this, things may work.',
'warning': 'If you change this, things may work.',
// a list of union selections. e.g. 'summer', 'winter',...
'default': [
'summer',
@@ -1102,7 +1102,7 @@ export module Mock {
},
'default': 'null',
'description': 'This is not even real.',
'change-warning': 'Be careful changing this!',
'warning': 'Be careful changing this!',
'values': [
'null',
'option1',
@@ -1115,7 +1115,7 @@ export module Mock {
'type': 'number',
'integral': false,
'description': 'Your favorite number of all time',
'change-warning': 'Once you set this number, it can never be changed without severe consequences.',
'warning': 'Once you set this number, it can never be changed without severe consequences.',
'nullable': false,
'default': 7,
'range': '(-100,100]',
@@ -1141,7 +1141,7 @@ export module Mock {
'type': 'object',
'unique-by': null,
'description': 'rpc username and password',
'change-warning': 'Adding RPC users gives them special permissions on your node.',
'warning': 'Adding RPC users gives them special permissions on your node.',
'spec': {
'laws': {
'name': 'Laws',
@@ -1268,7 +1268,7 @@ export module Mock {
'unique-by': null,
'description': 'The node settings',
'default': 'internal',
'change-warning': 'Careful changing this',
'warning': 'Careful changing this',
'tag': {
'id': 'type',
'name': 'Type',
@@ -1325,7 +1325,7 @@ export module Mock {
'type': 'list',
'subtype': 'string',
'description': 'external ip addresses that are authorized to access your Bitcoin node',
'change-warning': 'Any IP you allow here will have RPC access to your Bitcoin node.',
'warning': 'Any IP you allow here will have RPC access to your Bitcoin node.',
'range': '[1,10]',
'default': [
'192.168.1.1',

View File

@@ -27,13 +27,13 @@ export class FormService {
getUnionObject (spec: ValueSpecUnion | ListValueSpecUnion, selection: string, current?: { [key: string]: any }): FormGroup {
const { variants, tag } = spec
const { name, description, 'change-warning' : changeWarning } = isFullUnion(spec) ? spec : { ...spec.tag, 'change-warning': undefined }
const { name, description, warning } = isFullUnion(spec) ? spec : { ...spec.tag, warning: undefined }
const enumSpec: ValueSpecEnum = {
type: 'enum',
name,
description,
'change-warning': changeWarning,
warning,
default: selection,
values: Object.keys(variants),
'value-names': tag['variant-names'],

View File

@@ -163,7 +163,7 @@ export const serverConfig: ConfigSpec = {
// type: 'boolean',
// name: 'Tor Only Marketplace',
// description: `Use Start9's Tor (instead of clearnet) Marketplace.`,
// 'change-warning': 'This will result in higher latency and slower download times.',
// warning: 'This will result in higher latency and slower download times.',
// default: false,
// },
// 'package-marketplace': {
@@ -191,7 +191,7 @@ export const serverConfig: ConfigSpec = {
// // @TODO regex for 12 chars
// // pattern: '',
// 'pattern-description': 'Must contain at least 12 characters.',
// 'change-warning': 'If you forget your master password, there is absolutely no way to recover your data. This can result in loss of money! Keep in mind, old backups will still be encrypted by the password used to encrypt them.',
// warning: 'If you forget your master password, there is absolutely no way to recover your data. This can result in loss of money! Keep in mind, old backups will still be encrypted by the password used to encrypt them.',
// masked: false,
// copyable: false,
// },

View File

@@ -35,8 +35,8 @@ $subheader-height: 48px;
--min-height: #{$subheader-height};
}
.select-change-warning .alert-sub-title {
color: var(--ion-color-warning)
.select-warning .alert-sub-title {
color: var(--ion-color-warning);
}
.wide-alert {
@@ -183,8 +183,8 @@ ion-button {
position: absolute;
height: 80% !important;
top: 10%;
width: 60% !important;
left: 20%;
width: 50% !important;
left: 25%;
display: block;
}
}