mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
error flattening, remain to details, and delete old config stuff
This commit is contained in:
committed by
Aiden McClelland
parent
82928fe3ba
commit
a6cf6edcea
@@ -192,7 +192,7 @@ impl From<std::net::AddrParseError> for Error {
|
|||||||
impl From<Error> for RpcError {
|
impl From<Error> for RpcError {
|
||||||
fn from(e: Error) -> Self {
|
fn from(e: Error) -> Self {
|
||||||
let mut data_object = serde_json::Map::with_capacity(2);
|
let mut data_object = serde_json::Map::with_capacity(2);
|
||||||
data_object.insert("message".to_owned(), format!("{}", e.source).into());
|
data_object.insert("details".to_owned(), format!("{}", e.source).into());
|
||||||
data_object.insert(
|
data_object.insert(
|
||||||
"revision".to_owned(),
|
"revision".to_owned(),
|
||||||
match serde_json::to_value(&e.revision) {
|
match serde_json::to_value(&e.revision) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"build-prod": "ng build --prod && tsc postprocess.ts && node postprocess.js && cp client-manifest.yaml www && git log | head -n1 > www/git-hash.txt",
|
"build-prod": "ng build --prod && tsc postprocess.ts && node postprocess.js && git log | head -n1 > www/git-hash.txt",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"e2e": "ng e2e"
|
"e2e": "ng e2e"
|
||||||
|
|||||||
@@ -13,16 +13,13 @@ import { PatchDbServiceFactory } from './services/patch-db/patch-db.factory'
|
|||||||
import { HttpService } from './services/http.service'
|
import { HttpService } from './services/http.service'
|
||||||
import { ConfigService } from './services/config.service'
|
import { ConfigService } from './services/config.service'
|
||||||
import { QRCodeModule } from 'angularx-qrcode'
|
import { QRCodeModule } from 'angularx-qrcode'
|
||||||
import { appConfigComponents } from './modals/app-config-injectable'
|
|
||||||
import { OSWelcomePageModule } from './modals/os-welcome/os-welcome.module'
|
import { OSWelcomePageModule } from './modals/os-welcome/os-welcome.module'
|
||||||
import { MarkdownPageModule } from './modals/markdown/markdown.module'
|
import { MarkdownPageModule } from './modals/markdown/markdown.module'
|
||||||
import { PatchDbService } from './services/patch-db/patch-db.service'
|
import { PatchDbService } from './services/patch-db/patch-db.service'
|
||||||
import { LocalStorageBootstrap } from './services/patch-db/local-storage-bootstrap'
|
import { LocalStorageBootstrap } from './services/patch-db/local-storage-bootstrap'
|
||||||
import { SharingModule } from './modules/sharing.module'
|
import { SharingModule } from './modules/sharing.module'
|
||||||
import { MarketplaceApiService } from './services/api/marketplace/marketplace-api.service'
|
import { MarketplaceApiService } from './services/api/marketplace/marketplace-api.service'
|
||||||
import { APP_CONFIG_COMPONENT_MAPPING } from './services/sub-nav.service'
|
|
||||||
import { FormBuilder } from '@angular/forms'
|
import { FormBuilder } from '@angular/forms'
|
||||||
import { FormService } from './services/form.service'
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
@@ -51,7 +48,6 @@ import { FormService } from './services/form.service'
|
|||||||
{ provide: ApiService , useFactory: ApiServiceFactory, deps: [ConfigService, HttpService] }, { provide: ApiService , useFactory: ApiServiceFactory, deps: [ConfigService, HttpService] },
|
{ provide: ApiService , useFactory: ApiServiceFactory, deps: [ConfigService, HttpService] }, { provide: ApiService , useFactory: ApiServiceFactory, deps: [ConfigService, HttpService] },
|
||||||
{ provide: MarketplaceApiService , useFactory: MarketplaceApiServiceFactory, deps: [ConfigService, HttpService, PatchDbService] },
|
{ provide: MarketplaceApiService , useFactory: MarketplaceApiServiceFactory, deps: [ConfigService, HttpService, PatchDbService] },
|
||||||
{ provide: PatchDbService, useFactory: PatchDbServiceFactory, deps: [ConfigService, LocalStorageBootstrap, ApiService] },
|
{ provide: PatchDbService, useFactory: PatchDbServiceFactory, deps: [ConfigService, LocalStorageBootstrap, ApiService] },
|
||||||
{ provide: APP_CONFIG_COMPONENT_MAPPING, useValue: appConfigComponents },
|
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
|
schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { AppConfigObjectPage } from './app-config-object/app-config-object.page'
|
|
||||||
import { AppConfigListPage } from './app-config-list/app-config-list.page'
|
|
||||||
import { AppConfigUnionPage } from './app-config-union/app-config-union.page'
|
|
||||||
import { AppConfigValuePage } from './app-config-value/app-config-value.page'
|
|
||||||
import { Type } from '@angular/core'
|
|
||||||
import { ValueType } from 'src/app/pkg-config/config-types'
|
|
||||||
|
|
||||||
export const appConfigComponents: AppConfigComponentMapping = {
|
|
||||||
'string': AppConfigValuePage,
|
|
||||||
'number': AppConfigValuePage,
|
|
||||||
'enum': AppConfigValuePage,
|
|
||||||
'boolean': AppConfigValuePage,
|
|
||||||
'list': AppConfigListPage,
|
|
||||||
'object': AppConfigObjectPage,
|
|
||||||
'union': AppConfigUnionPage,
|
|
||||||
'pointer': undefined,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AppConfigComponentMapping = { [k in ValueType]: Type<any> }
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { AppConfigListPage } from './app-config-list.page'
|
|
||||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
|
||||||
import { ConfigHeaderComponentModule } from 'src/app/components/config-header/config-header.component.module'
|
|
||||||
import { FormsModule } from '@angular/forms'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [AppConfigListPage],
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
IonicModule,
|
|
||||||
SharingModule,
|
|
||||||
FormsModule,
|
|
||||||
ConfigHeaderComponentModule,
|
|
||||||
],
|
|
||||||
entryComponents: [AppConfigListPage],
|
|
||||||
exports: [AppConfigListPage],
|
|
||||||
})
|
|
||||||
export class AppConfigListPageModule { }
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<ion-content class="subheader-padding">
|
|
||||||
|
|
||||||
<config-header [spec]="spec" [error]="error"></config-header>
|
|
||||||
|
|
||||||
<ion-button *ngIf="spec.subtype !== 'enum'" (click)="createOrEdit()">
|
|
||||||
<ion-icon name="add" color="primary"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
|
|
||||||
<!-- enum list -->
|
|
||||||
<ion-item-group *ngIf="spec.subtype === 'enum'">
|
|
||||||
<ion-item-divider class="borderless"></ion-item-divider>
|
|
||||||
<ion-item-divider>
|
|
||||||
{{ value.length }} selected
|
|
||||||
<span *ngIf="min"> (min: {{ min }})</span>
|
|
||||||
<span *ngIf="max"> (max: {{ max }})</span>
|
|
||||||
<ion-button slot="end" fill="clear" color="primary" (click)="toggleSelectAll()">{{ selectAll ? 'All' : 'None' }}</ion-button>
|
|
||||||
</ion-item-divider>
|
|
||||||
<ion-item *ngFor="let option of options">
|
|
||||||
<ion-label>{{ option.value }}</ion-label>
|
|
||||||
<ion-checkbox slot="end" [(ngModel)]="option.checked" (click)="toggleSelected(option.value)"></ion-checkbox>
|
|
||||||
</ion-item>
|
|
||||||
</ion-item-group>
|
|
||||||
|
|
||||||
<!-- not enum list -->
|
|
||||||
<div *ngIf="spec.subtype !== 'enum'">
|
|
||||||
<ion-item-divider class="borderless"></ion-item-divider>
|
|
||||||
<ion-item-group>
|
|
||||||
<ion-item-divider style="font-size: small; color: var(--ion-color-medium);">
|
|
||||||
{{ value.length }}
|
|
||||||
<span *ngIf="value.length === 1">Entry</span>
|
|
||||||
<span *ngIf="value.length !== 1">Entries</span>
|
|
||||||
<span *ngIf="min"> (min: {{ min }})</span>
|
|
||||||
<span *ngIf="max"> (max: {{ max }})</span>
|
|
||||||
</ion-item-divider>
|
|
||||||
|
|
||||||
<div *ngFor="let v of value; index as i;">
|
|
||||||
<ion-item button detail="false" (click)="createOrEdit(i)">
|
|
||||||
<ion-icon size="small" slot="start" *ngIf="!annotations.members[i] || (annotations.members[i] | annotationStatus: 'NoChange')" style="margin-right: 15px; color: rgba(0,0,0,0); background: radial-gradient(#2a4e8970, #2a4e8970 35%, transparent 35%, transparent);" name="ellipse"></ion-icon>
|
|
||||||
<ion-icon size="small" slot="start" *ngIf="!annotations.members[i] || (annotations.members[i] | annotationStatus: 'Added')" style="margin-right: 15px; color: rgba(0,0,0,0); background: radial-gradient(#2a4e8970, #2a4e8970 35%, transparent 35%, transparent);" name="ellipse"></ion-icon>
|
|
||||||
<ion-icon size="small" slot="start" *ngIf="annotations.members[i] && (annotations.members[i] | annotationStatus: 'Edited')" style="margin-right: 15px" color="primary" name="ellipse"></ion-icon>
|
|
||||||
<ion-icon size="small" slot="start" *ngIf="annotations.members[i] && (annotations.members[i] | annotationStatus: 'Invalid')" style="margin-right: 15px" color="danger" name="warning-outline"></ion-icon>
|
|
||||||
|
|
||||||
<ion-label>{{ valueString[i] }}</ion-label>
|
|
||||||
|
|
||||||
<ion-button slot="end" fill="clear" (click)="presentAlertDelete(i, $event)">
|
|
||||||
<ion-icon slot="icon-only" name="close"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</ion-item>
|
|
||||||
</div>
|
|
||||||
</ion-item-group>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
import { Component, Input } from '@angular/core'
|
|
||||||
import { AlertController, IonNav } from '@ionic/angular'
|
|
||||||
import { Annotations, Range } from '../../pkg-config/config-utilities'
|
|
||||||
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
|
|
||||||
import { ValueSpecList, isValueSpecListOf } from 'src/app/pkg-config/config-types'
|
|
||||||
import { SubNavService } from 'src/app/services/sub-nav.service'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-config-list',
|
|
||||||
templateUrl: './app-config-list.page.html',
|
|
||||||
styleUrls: ['./app-config-list.page.scss'],
|
|
||||||
})
|
|
||||||
export class AppConfigListPage {
|
|
||||||
@Input() cursor: ConfigCursor<'list'>
|
|
||||||
|
|
||||||
spec: ValueSpecList
|
|
||||||
value: string[] | number[] | object[]
|
|
||||||
valueString: string[]
|
|
||||||
annotations: Annotations<'list'>
|
|
||||||
|
|
||||||
// enum only
|
|
||||||
options: { value: string, checked: boolean }[] = []
|
|
||||||
selectAll = true
|
|
||||||
|
|
||||||
min: number | undefined
|
|
||||||
max: number | undefined
|
|
||||||
|
|
||||||
minMessage: string
|
|
||||||
maxMessage: string
|
|
||||||
|
|
||||||
error: string
|
|
||||||
|
|
||||||
constructor (
|
|
||||||
private readonly alertCtrl: AlertController,
|
|
||||||
private readonly subNav: SubNavService,
|
|
||||||
private readonly nav: IonNav,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit () {
|
|
||||||
this.spec = this.cursor.spec()
|
|
||||||
this.value = this.cursor.config()
|
|
||||||
const range = Range.from(this.spec.range)
|
|
||||||
this.min = range.integralMin()
|
|
||||||
this.max = range.integralMax()
|
|
||||||
this.minMessage = `The minimum number of ${this.cursor.key()} is ${this.min}.`
|
|
||||||
this.maxMessage = `The maximum number of ${this.cursor.key()} is ${this.max}.`
|
|
||||||
// enum list only
|
|
||||||
if (isValueSpecListOf(this.spec, 'enum')) {
|
|
||||||
for (let val of this.spec.spec.values) {
|
|
||||||
this.options.push({
|
|
||||||
value: val,
|
|
||||||
checked: (this.value as string[]).includes(val),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.updateCaches()
|
|
||||||
}
|
|
||||||
|
|
||||||
// enum only
|
|
||||||
toggleSelectAll () {
|
|
||||||
if (!isValueSpecListOf(this.spec, 'enum')) { throw new Error('unreachable') }
|
|
||||||
|
|
||||||
this.value.length = 0
|
|
||||||
if (this.selectAll) {
|
|
||||||
for (let v of this.spec.spec.values) {
|
|
||||||
(this.value as string[]).push(v)
|
|
||||||
}
|
|
||||||
for (let option of this.options) {
|
|
||||||
option.checked = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (let option of this.options) {
|
|
||||||
option.checked = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.updateCaches()
|
|
||||||
}
|
|
||||||
|
|
||||||
// enum only
|
|
||||||
async toggleSelected (value: string) {
|
|
||||||
const index = (this.value as string[]).indexOf(value)
|
|
||||||
|
|
||||||
// if present, delete
|
|
||||||
if (index > -1) {
|
|
||||||
(this.value as string[]).splice(index, 1)
|
|
||||||
// if not present, add
|
|
||||||
} else {
|
|
||||||
(this.value as string[]).push(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateCaches()
|
|
||||||
}
|
|
||||||
|
|
||||||
async createOrEdit (index?: number) {
|
|
||||||
const nextCursor = this.cursor.seekNext(index === undefined ? this.value.length : index)
|
|
||||||
nextCursor.createFirstEntryForList()
|
|
||||||
this.subNav.push(String(index), nextCursor, this.nav)
|
|
||||||
}
|
|
||||||
|
|
||||||
async presentAlertDelete (key: number, e: Event) {
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
const alert = await this.alertCtrl.create({
|
|
||||||
backdropDismiss: false,
|
|
||||||
header: 'Caution',
|
|
||||||
message: `Are you sure you want to delete this entry?`,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: 'Cancel',
|
|
||||||
role: 'cancel',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Delete',
|
|
||||||
handler: () => {
|
|
||||||
if (typeof key === 'number') {
|
|
||||||
(this.value as any[]).splice(key, 1)
|
|
||||||
} else {
|
|
||||||
delete this.value[key]
|
|
||||||
}
|
|
||||||
this.updateCaches()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
await alert.present()
|
|
||||||
}
|
|
||||||
|
|
||||||
asIsOrder () {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateCaches () {
|
|
||||||
if (isValueSpecListOf(this.spec, 'enum')) {
|
|
||||||
this.selectAll = this.value.length !== this.spec.spec.values.length
|
|
||||||
}
|
|
||||||
this.error = this.cursor.checkInvalid()
|
|
||||||
this.annotations = this.cursor.getAnnotations()
|
|
||||||
this.valueString = (this.value as any[]).map((_, idx) => this.cursor.seekNext(idx).toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { AppConfigObjectPage } from './app-config-object.page'
|
|
||||||
import { ObjectConfigComponentModule } from 'src/app/components/object-config/object-config.component.module'
|
|
||||||
import { ConfigHeaderComponentModule } from 'src/app/components/config-header/config-header.component.module'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [AppConfigObjectPage],
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
IonicModule,
|
|
||||||
ObjectConfigComponentModule,
|
|
||||||
ConfigHeaderComponentModule,
|
|
||||||
],
|
|
||||||
entryComponents: [AppConfigObjectPage],
|
|
||||||
exports: [AppConfigObjectPage],
|
|
||||||
})
|
|
||||||
export class AppConfigObjectPageModule { }
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<ion-content class="subheader-padding">
|
|
||||||
<config-header [spec]="spec" [error]="error"></config-header>
|
|
||||||
<object-config [cursor]="cursor" (onEdit)="handleObjectEdit()" (onClick)="updatePath($event)"></object-config>
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import { Component, Input } from '@angular/core'
|
|
||||||
import { ModalController, AlertController } from '@ionic/angular'
|
|
||||||
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
|
|
||||||
import { ValueSpecObject } from 'src/app/pkg-config/config-types'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-config-object',
|
|
||||||
templateUrl: './app-config-object.page.html',
|
|
||||||
styleUrls: ['./app-config-object.page.scss'],
|
|
||||||
})
|
|
||||||
export class AppConfigObjectPage {
|
|
||||||
@Input() cursor: ConfigCursor<'object'>
|
|
||||||
spec: ValueSpecObject
|
|
||||||
value: object
|
|
||||||
error: string
|
|
||||||
|
|
||||||
constructor (
|
|
||||||
private readonly modalCtrl: ModalController,
|
|
||||||
private readonly alertCtrl: AlertController,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit () {
|
|
||||||
this.spec = this.cursor.spec()
|
|
||||||
this.value = this.cursor.config()
|
|
||||||
this.error = this.cursor.checkInvalid()
|
|
||||||
}
|
|
||||||
|
|
||||||
async dismiss (nullify = false) {
|
|
||||||
this.modalCtrl.dismiss(nullify ? null : this.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
async presentAlertDestroy () {
|
|
||||||
const alert = await this.alertCtrl.create({
|
|
||||||
backdropDismiss: false,
|
|
||||||
header: 'Caution',
|
|
||||||
message: 'Are you sure you want to delete this record?',
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: 'Cancel',
|
|
||||||
role: 'cancel',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Delete',
|
|
||||||
handler: () => {
|
|
||||||
this.dismiss(true)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
await alert.present()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleObjectEdit () {
|
|
||||||
this.error = this.cursor.checkInvalid()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { AppConfigUnionPage } from './app-config-union.page'
|
|
||||||
import { ObjectConfigComponentModule } from 'src/app/components/object-config/object-config.component.module'
|
|
||||||
import { FormsModule } from '@angular/forms'
|
|
||||||
import { ConfigHeaderComponentModule } from 'src/app/components/config-header/config-header.component.module'
|
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [AppConfigUnionPage],
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
IonicModule,
|
|
||||||
FormsModule,
|
|
||||||
ObjectConfigComponentModule,
|
|
||||||
ConfigHeaderComponentModule,
|
|
||||||
],
|
|
||||||
entryComponents: [AppConfigUnionPage],
|
|
||||||
exports: [AppConfigUnionPage],
|
|
||||||
})
|
|
||||||
export class AppConfigUnionPageModule { }
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<ion-content class="subheader-padding">
|
|
||||||
|
|
||||||
<config-header [spec]="spec" [error]="error"></config-header>
|
|
||||||
|
|
||||||
<!-- union -->
|
|
||||||
<ion-item-group>
|
|
||||||
<ion-item-divider></ion-item-divider>
|
|
||||||
<ion-item>
|
|
||||||
<ion-icon size="small" slot="start" *ngIf="!edited"
|
|
||||||
style="margin-right: 15px; color: rgba(0,0,0,0); background: radial-gradient(#2a4e8970, #2a4e8970 35%, transparent 35%, transparent);"
|
|
||||||
name="ellipse"></ion-icon>
|
|
||||||
<ion-icon size="small" slot="start" *ngIf="edited" style="margin-right: 15px" color="primary" name="ellipse">
|
|
||||||
</ion-icon>
|
|
||||||
<ion-label>{{ spec.tag.name }}</ion-label>
|
|
||||||
<ion-select slot="end" [interfaceOptions]="setSelectOptions()" placeholder="Select One"
|
|
||||||
[(ngModel)]="value[spec.tag.id]" [selectedText]="spec.tag['variant-names'][value[spec.tag.id]]"
|
|
||||||
(ngModelChange)="handleUnionChange()">
|
|
||||||
<ion-select-option *ngFor="let option of spec.variants | keyvalue: asIsOrder" [value]="option.key">
|
|
||||||
{{ spec.tag['variant-names'][option.key] }}
|
|
||||||
<span *ngIf="option.key === spec.default"> (default)</span>
|
|
||||||
</ion-select-option>
|
|
||||||
</ion-select>
|
|
||||||
</ion-item>
|
|
||||||
<object-config [cursor]="cursor" (onEdit)="handleObjectEdit()"></object-config>
|
|
||||||
</ion-item-group>
|
|
||||||
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { Component, Input, ViewChild } from '@angular/core'
|
|
||||||
import { ModalController } from '@ionic/angular'
|
|
||||||
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
|
|
||||||
import { ValueSpecUnion } from 'src/app/pkg-config/config-types'
|
|
||||||
import { ObjectConfigComponent } from 'src/app/components/object-config/object-config.component'
|
|
||||||
import { mapUnionSpec } from '../../pkg-config/config-utilities'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-config-union',
|
|
||||||
templateUrl: './app-config-union.page.html',
|
|
||||||
styleUrls: ['./app-config-union.page.scss'],
|
|
||||||
})
|
|
||||||
export class AppConfigUnionPage {
|
|
||||||
@Input() cursor: ConfigCursor<'union'>
|
|
||||||
|
|
||||||
@ViewChild(ObjectConfigComponent)
|
|
||||||
objectConfig: ObjectConfigComponent
|
|
||||||
|
|
||||||
spec: ValueSpecUnion
|
|
||||||
value: object
|
|
||||||
error: string
|
|
||||||
edited: boolean
|
|
||||||
|
|
||||||
constructor (
|
|
||||||
private readonly modalCtrl: ModalController,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit () {
|
|
||||||
this.spec = this.cursor.spec()
|
|
||||||
this.value = this.cursor.config()
|
|
||||||
this.error = this.cursor.checkInvalid()
|
|
||||||
this.edited = this.cursor.seekNext(this.spec.tag.id).isEdited()
|
|
||||||
}
|
|
||||||
|
|
||||||
async dismiss () {
|
|
||||||
this.modalCtrl.dismiss(this.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleUnionChange () {
|
|
||||||
this.value = mapUnionSpec(this.spec, this.value)
|
|
||||||
this.objectConfig.annotations = this.objectConfig.cursor.getAnnotations()
|
|
||||||
this.error = this.cursor.checkInvalid()
|
|
||||||
this.edited = this.cursor.seekNext(this.spec.tag.id).isEdited()
|
|
||||||
}
|
|
||||||
|
|
||||||
setSelectOptions () {
|
|
||||||
return {
|
|
||||||
header: this.spec.tag.name,
|
|
||||||
subHeader: this.spec['change-warning'] ? 'Warning!' : undefined,
|
|
||||||
message: this.spec['change-warning'] ? `${this.spec['change-warning']}` : undefined,
|
|
||||||
cssClass: 'select-change-warning',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleObjectEdit () {
|
|
||||||
this.error = this.cursor.checkInvalid()
|
|
||||||
}
|
|
||||||
|
|
||||||
asIsOrder () {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { FormsModule } from '@angular/forms'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { AppConfigValuePage } from './app-config-value.page'
|
|
||||||
import { ConfigHeaderComponentModule } from 'src/app/components/config-header/config-header.component.module'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [AppConfigValuePage],
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
FormsModule,
|
|
||||||
IonicModule,
|
|
||||||
ConfigHeaderComponentModule,
|
|
||||||
],
|
|
||||||
entryComponents: [AppConfigValuePage],
|
|
||||||
exports: [AppConfigValuePage],
|
|
||||||
})
|
|
||||||
export class AppConfigValuePageModule { }
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<ion-content class="subheader-padding">
|
|
||||||
|
|
||||||
<config-header [spec]="spec" [error]="error"></config-header>
|
|
||||||
|
|
||||||
<ion-item-group>
|
|
||||||
<ion-item-divider>
|
|
||||||
<ion-button *ngIf="spec.type === 'string' && spec.copyable" style="padding-right: 12px;" size="small" slot="end" fill="clear" (click)="copy()">
|
|
||||||
<ion-icon slot="icon-only" name="copy-outline" size="small"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</ion-item-divider>
|
|
||||||
<!-- string -->
|
|
||||||
<ion-item *ngIf="spec.type === 'string'">
|
|
||||||
<ion-input [type]="spec.masked && !unmasked ? 'password' : 'text'" placeholder="Enter value" [(ngModel)]="value" (ngModelChange)="handleInput()"></ion-input>
|
|
||||||
<div slot="end">
|
|
||||||
<ion-button *ngIf="spec.masked" fill="clear" [color]="unmasked ? 'danger' : 'primary'" (click)="toggleMask()">
|
|
||||||
<ion-icon slot="icon-only" [name]="unmasked ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
<ion-button *ngIf="value && spec.nullable" fill="clear" (click)="clear()">
|
|
||||||
<ion-icon slot="icon-only" name="close" size="small"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</div>
|
|
||||||
</ion-item>
|
|
||||||
<!-- number -->
|
|
||||||
<ion-item *ngIf="spec.type === 'number'">
|
|
||||||
<ion-input type="tel" placeholder="Enter value" [(ngModel)]="value" (ngModelChange)="handleInput()"></ion-input>
|
|
||||||
<span slot="end" *ngIf="spec.units"><ion-text>{{ spec.units }}</ion-text></span>
|
|
||||||
<ion-button *ngIf="value && spec.nullable" slot="end" fill="clear" (click)="clear()">
|
|
||||||
<ion-icon slot="icon-only" name="close" size="small"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</ion-item>
|
|
||||||
<!-- boolean -->
|
|
||||||
<ion-item *ngIf="spec.type === 'boolean'">
|
|
||||||
<ion-label>{{ spec.name }}</ion-label>
|
|
||||||
<ion-toggle slot="end" [(ngModel)]="value" (ngModelChange)="edited = true"></ion-toggle>
|
|
||||||
</ion-item>
|
|
||||||
<!-- enum -->
|
|
||||||
<ion-list *ngIf="spec.type === 'enum'">
|
|
||||||
<ion-radio-group [(ngModel)]="value">
|
|
||||||
<ion-item *ngFor="let option of spec.values">
|
|
||||||
<ion-label>{{ spec['value-names'][option] }}</ion-label>
|
|
||||||
<ion-radio slot="start" [value]="option"></ion-radio>
|
|
||||||
</ion-item>
|
|
||||||
</ion-radio-group>
|
|
||||||
</ion-list>
|
|
||||||
<!-- metadata -->
|
|
||||||
<div class="ion-padding-start">
|
|
||||||
<p *ngIf="spec.type === 'string' && spec['pattern-description']">
|
|
||||||
{{ spec['pattern-description'] }}
|
|
||||||
</p>
|
|
||||||
<p *ngIf="spec.type === 'number' && spec.integral">
|
|
||||||
{{ integralDescription }}
|
|
||||||
</p>
|
|
||||||
<p *ngIf="rangeDescription">
|
|
||||||
{{ rangeDescription }}
|
|
||||||
</p>
|
|
||||||
<ng-container *ngIf="spec.default !== undefined">
|
|
||||||
<p>Default: {{ defaultDescription }} <ion-icon style="padding-left: 8px;" name="refresh-outline" color="dark" style="cursor: pointer;" (click)="refreshDefault()"></ion-icon></p>
|
|
||||||
<p *ngIf="spec.type === 'number' && spec.units">Units: {{ spec.units }}</p>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</ion-item-group>
|
|
||||||
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
import { Component, Input } from '@angular/core'
|
|
||||||
import { getDefaultConfigValue, getDefaultDescription, Range } from 'src/app/pkg-config/config-utilities'
|
|
||||||
import { AlertController, LoadingController, ModalController, ToastController } from '@ionic/angular'
|
|
||||||
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
|
|
||||||
import { ValueSpecOf } from 'src/app/pkg-config/config-types'
|
|
||||||
import { copyToClipboard } from 'src/app/util/web.util'
|
|
||||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-config-value',
|
|
||||||
templateUrl: 'app-config-value.page.html',
|
|
||||||
styleUrls: ['app-config-value.page.scss'],
|
|
||||||
})
|
|
||||||
export class AppConfigValuePage {
|
|
||||||
@Input() cursor: ConfigCursor<'string' | 'number' | 'boolean' | 'enum'>
|
|
||||||
@Input() saveFn?: (value: string | number | boolean) => Promise<any>
|
|
||||||
|
|
||||||
spec: ValueSpecOf<'string' | 'number' | 'boolean' | 'enum'>
|
|
||||||
value: string | number | boolean | null
|
|
||||||
|
|
||||||
edited: boolean
|
|
||||||
error: string
|
|
||||||
unmasked = false
|
|
||||||
|
|
||||||
defaultDescription: string
|
|
||||||
integralDescription = 'Value must be a whole number.'
|
|
||||||
|
|
||||||
range: Range
|
|
||||||
rangeDescription: string
|
|
||||||
|
|
||||||
constructor (
|
|
||||||
private readonly loadingCtrl: LoadingController,
|
|
||||||
private readonly modalCtrl: ModalController,
|
|
||||||
private readonly alertCtrl: AlertController,
|
|
||||||
private readonly toastCtrl: ToastController,
|
|
||||||
private readonly errToast: ErrorToastService,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit () {
|
|
||||||
this.spec = this.cursor.spec()
|
|
||||||
this.value = this.cursor.config()
|
|
||||||
this.error = this.cursor.checkInvalid()
|
|
||||||
|
|
||||||
this.defaultDescription = getDefaultDescription(this.spec)
|
|
||||||
if (this.spec.type === 'number') {
|
|
||||||
this.range = Range.from(this.spec.range)
|
|
||||||
this.rangeDescription = this.range.description()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async dismiss () {
|
|
||||||
if (this.value === '') this.value = null
|
|
||||||
|
|
||||||
if (this.spec.type === 'number' && this.value !== null) {
|
|
||||||
this.value = Number(this.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((!!this.saveFn && this.edited) || (!this.saveFn && this.error)) {
|
|
||||||
await this.presentAlert()
|
|
||||||
} else {
|
|
||||||
await this.modalCtrl.dismiss(this.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async save () {
|
|
||||||
if (this.value === '') this.value = null
|
|
||||||
|
|
||||||
if (this.spec.type === 'number' && this.value !== null) {
|
|
||||||
this.value = Number(this.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const loader = await this.loadingCtrl.create({
|
|
||||||
spinner: 'lines',
|
|
||||||
message: 'Saving...',
|
|
||||||
cssClass: 'loader',
|
|
||||||
})
|
|
||||||
await loader.present()
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.saveFn(this.value)
|
|
||||||
this.modalCtrl.dismiss(this.value)
|
|
||||||
} catch (e) {
|
|
||||||
this.errToast.present(e)
|
|
||||||
} finally {
|
|
||||||
loader.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshDefault () {
|
|
||||||
this.value = getDefaultConfigValue(this.spec) as any
|
|
||||||
this.handleInput()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleInput () {
|
|
||||||
this.validate()
|
|
||||||
this.edited = true
|
|
||||||
}
|
|
||||||
|
|
||||||
clear () {
|
|
||||||
this.value = null
|
|
||||||
this.edited = true
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleMask () {
|
|
||||||
this.unmasked = !this.unmasked
|
|
||||||
}
|
|
||||||
|
|
||||||
async copy (): Promise<void> {
|
|
||||||
let message = ''
|
|
||||||
await copyToClipboard(String(this.value)).then(success => { message = success ? 'copied to clipboard!' : 'failed to copy'})
|
|
||||||
|
|
||||||
const toast = await this.toastCtrl.create({
|
|
||||||
header: message,
|
|
||||||
position: 'bottom',
|
|
||||||
duration: 1000,
|
|
||||||
})
|
|
||||||
await toast.present()
|
|
||||||
}
|
|
||||||
|
|
||||||
private validate (): boolean {
|
|
||||||
if (this.spec.type === 'boolean') return true
|
|
||||||
|
|
||||||
// test blank
|
|
||||||
if (this.value === '' && !(this.spec as any).nullable) {
|
|
||||||
this.error = 'Value cannot be blank'
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// test pattern if string
|
|
||||||
if (this.spec.type === 'string' && this.value) {
|
|
||||||
const { pattern, 'pattern-description' : patternDescription } = this.spec
|
|
||||||
if (pattern && !RegExp(pattern).test(this.value as string)) {
|
|
||||||
this.error = patternDescription || `Must match ${pattern}`
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// test range if number
|
|
||||||
if (this.spec.type === 'number' && this.value) {
|
|
||||||
if (this.spec.integral && !RegExp(/^[-+]?[0-9]+$/).test(String(this.value))) {
|
|
||||||
this.error = this.integralDescription
|
|
||||||
return false
|
|
||||||
} else if (!this.spec.integral && !RegExp(/^[0-9]*\.?[0-9]+$/).test(String(this.value))) {
|
|
||||||
this.error = 'Value must be a number.'
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
this.range.checkIncludes(Number(this.value))
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(e) //an invalid spec is not an error
|
|
||||||
this.error = e.message
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.error = ''
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private async presentAlert () {
|
|
||||||
const header = this.error ?
|
|
||||||
'Invalid Entry' :
|
|
||||||
'Unsaved Changes'
|
|
||||||
|
|
||||||
const message = this.error ?
|
|
||||||
'Value will not be saved' :
|
|
||||||
'You have unsaved changes. Are you sure you want to leave?'
|
|
||||||
|
|
||||||
const alert = await this.alertCtrl.create({
|
|
||||||
backdropDismiss: false,
|
|
||||||
header,
|
|
||||||
message,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: 'Cancel',
|
|
||||||
role: 'cancel',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `Leave`,
|
|
||||||
handler: () => {
|
|
||||||
this.modalCtrl.dismiss()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
await alert.present()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -53,4 +53,8 @@ export class EnumListPage {
|
|||||||
async toggleSelected (key: string) {
|
async toggleSelected (key: string) {
|
||||||
this.options[key] = !this.options[key]
|
this.options[key] = !this.options[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asIsOrder () {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { AppConfigListPageModule } from 'src/app/modals/app-config-list/app-config-list.module'
|
|
||||||
import { AppConfigObjectPageModule } from 'src/app/modals/app-config-object/app-config-object.module'
|
|
||||||
import { AppConfigUnionPageModule } from 'src/app/modals/app-config-union/app-config-union.module'
|
|
||||||
import { AppConfigValuePageModule } from 'src/app/modals/app-config-value/app-config-value.module'
|
|
||||||
import { SubNavComponentModule } from '../components/sub-nav/sub-nav.component.module'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
AppConfigListPageModule,
|
|
||||||
AppConfigObjectPageModule,
|
|
||||||
AppConfigUnionPageModule,
|
|
||||||
AppConfigValuePageModule,
|
|
||||||
SubNavComponentModule,
|
|
||||||
],
|
|
||||||
exports: [
|
|
||||||
AppConfigListPageModule,
|
|
||||||
AppConfigObjectPageModule,
|
|
||||||
AppConfigUnionPageModule,
|
|
||||||
AppConfigValuePageModule,
|
|
||||||
SubNavComponentModule,
|
|
||||||
],
|
|
||||||
})
|
|
||||||
export class SubNavModule { }
|
|
||||||
@@ -2,6 +2,7 @@ import { Subject, Observable } from 'rxjs'
|
|||||||
import { Http, Update, Operation, Revision, Source, Store } from 'patch-db-client'
|
import { Http, Update, Operation, Revision, Source, Store } from 'patch-db-client'
|
||||||
import { RR } from '../api.types'
|
import { RR } from '../api.types'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
|
import { RequestError, RPCError } from '../../http.service'
|
||||||
|
|
||||||
export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
|
export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
|
||||||
protected readonly sync = new Subject<Update<DataModel>>()
|
protected readonly sync = new Subject<Update<DataModel>>()
|
||||||
@@ -189,10 +190,15 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
|
|||||||
// this.sync.next({ patch: [temp], expiredBy: expireId })
|
// this.sync.next({ patch: [temp], expiredBy: expireId })
|
||||||
// }
|
// }
|
||||||
|
|
||||||
return f(a).then(({ response, revision }) => {
|
return f(a)
|
||||||
if (revision) this.sync.next(revision)
|
.catch((e: RequestError) => {
|
||||||
return response
|
if (e.revision) this.sync.next(e.revision)
|
||||||
}) as any
|
throw e
|
||||||
|
})
|
||||||
|
.then(({ response, revision }) => {
|
||||||
|
if (revision) this.sync.next(revision)
|
||||||
|
return response
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ export class ErrorToastService {
|
|||||||
|
|
||||||
let message: string | IonicSafeString
|
let message: string | IonicSafeString
|
||||||
|
|
||||||
if (e.status) message = String(e.status)
|
if (e.code) message = String(e.code)
|
||||||
if (e.message) message = `${message ? message + ' ' : ''}${e.message}`
|
if (e.message) message = `${message ? message + ' ' : ''}${e.message}`
|
||||||
if (e.data) message = `${message ? message + '. ' : ''}${e.data.code}: ${e.data.message}`
|
if (e.details) message = `${message ? message + ': ' : ''}${e.details}`
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
message = 'Unknown Error.'
|
message = 'Unknown Error.'
|
||||||
|
|||||||
@@ -16,9 +16,8 @@ export class HttpService {
|
|||||||
private readonly http: HttpClient,
|
private readonly http: HttpClient,
|
||||||
private readonly config: ConfigService,
|
private readonly config: ConfigService,
|
||||||
) {
|
) {
|
||||||
const { url, version } = this.config.api
|
|
||||||
const port = config.mocks.enabled ? this.config.mocks.rpcPort : window.location.port
|
const port = config.mocks.enabled ? this.config.mocks.rpcPort : window.location.port
|
||||||
this.fullUrl = `${window.location.protocol}//${window.location.hostname}:${port}/${url}/${version}`
|
this.fullUrl = `${window.location.protocol}//${window.location.hostname}:${port}`
|
||||||
}
|
}
|
||||||
|
|
||||||
watchUnauth$ (): Observable<{ }> {
|
watchUnauth$ (): Observable<{ }> {
|
||||||
@@ -26,11 +25,12 @@ export class HttpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async rpcRequest<T> (rpcOpts: RPCOptions): Promise<T> {
|
async rpcRequest<T> (rpcOpts: RPCOptions): Promise<T> {
|
||||||
|
const { url, version } = this.config.api
|
||||||
rpcOpts.params = rpcOpts.params || { }
|
rpcOpts.params = rpcOpts.params || { }
|
||||||
const httpOpts = {
|
const httpOpts = {
|
||||||
method: Method.POST,
|
method: Method.POST,
|
||||||
data: rpcOpts,
|
data: rpcOpts,
|
||||||
url: '',
|
url: `/${url}/${version}`,
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await this.httpRequest<RPCResponse<T>>(httpOpts)
|
const res = await this.httpRequest<RPCResponse<T>>(httpOpts)
|
||||||
@@ -91,21 +91,27 @@ export class HttpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function RpcError (e: RPCError['error']): void {
|
function RpcError (e: RPCError['error']): void {
|
||||||
const { code, message } = e
|
const { code, message, data } = e
|
||||||
this.status = code
|
|
||||||
|
this.code = code
|
||||||
this.message = message
|
this.message = message
|
||||||
if (typeof e.data === 'string') {
|
|
||||||
throw new Error(`unexpected response for RPC Error data: ${e.data}`)
|
if (typeof data === 'string') {
|
||||||
|
this.details = e.data
|
||||||
|
this.revision = null
|
||||||
|
} else {
|
||||||
|
this.details = data.details
|
||||||
|
this.revision = data.revision
|
||||||
}
|
}
|
||||||
const data = e.data || { message: 'unknown RPC error', revision: null }
|
|
||||||
this.data = { ...data, code }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function HttpError (e: HttpErrorResponse): void {
|
function HttpError (e: HttpErrorResponse): void {
|
||||||
const { status, statusText, error } = e
|
const { status, statusText } = e
|
||||||
this.status = status
|
|
||||||
|
this.code = status
|
||||||
this.message = statusText
|
this.message = statusText
|
||||||
this.data = error || { } // error = { code: string, message: string }
|
this.details = null
|
||||||
|
this.revision = null
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRpcError<Error, Result> (arg: { error: Error } | { result: Result}): arg is { error: Error } {
|
function isRpcError<Error, Result> (arg: { error: Error } | { result: Result}): arg is { error: Error } {
|
||||||
@@ -117,9 +123,10 @@ function isRpcSuccess<Error, Result> (arg: { error: Error } | { result: Result})
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestError {
|
export interface RequestError {
|
||||||
status: number
|
code: number
|
||||||
message: string
|
message: string
|
||||||
data: { code: string, message: string, revision: Revision | null }
|
details: string
|
||||||
|
revision: Revision | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Method {
|
export enum Method {
|
||||||
@@ -157,7 +164,7 @@ export interface RPCError extends RPCBase {
|
|||||||
code: number,
|
code: number,
|
||||||
message: string
|
message: string
|
||||||
data?: {
|
data?: {
|
||||||
message: string
|
details: string
|
||||||
revision: Revision | null
|
revision: Revision | null
|
||||||
} | string
|
} | string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,16 @@
|
|||||||
import { Inject, Injectable, InjectionToken } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { IonNav } from '@ionic/angular'
|
import { IonNav } from '@ionic/angular'
|
||||||
import { AppConfigComponentMapping } from '../modals/app-config-injectable'
|
|
||||||
import { ConfigCursor } from '../pkg-config/config-cursor'
|
import { ConfigCursor } from '../pkg-config/config-cursor'
|
||||||
|
|
||||||
export const APP_CONFIG_COMPONENT_MAPPING = new InjectionToken<string>('APP_CONFIG_COMPONENTS')
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class SubNavService {
|
export class SubNavService {
|
||||||
path: string[]
|
path: string[]
|
||||||
|
|
||||||
constructor (
|
|
||||||
@Inject(APP_CONFIG_COMPONENT_MAPPING) private readonly appConfigComponentMapping: AppConfigComponentMapping,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
async push (key: string, cursor: ConfigCursor<any>, nav: IonNav) {
|
async push (key: string, cursor: ConfigCursor<any>, nav: IonNav) {
|
||||||
const component = this.appConfigComponentMapping[cursor.spec().type]
|
|
||||||
this.path.push(key)
|
this.path.push(key)
|
||||||
nav.push(component, { cursor }, { mode: 'ios' })
|
// nav.push(component, { cursor }, { mode: 'ios' })
|
||||||
}
|
}
|
||||||
|
|
||||||
async pop (nav: IonNav) {
|
async pop (nav: IonNav) {
|
||||||
|
|||||||
Reference in New Issue
Block a user