* begin subnav implementation

* implement subnav AND angular forms for comparison

* unions working-ish, list of enums working

* new form approach almost complete

* finish new forms approach for action inputs and config

* expandable list items and handlebars display

* Config animation (#394)

* config cammel

* config animation

Co-authored-by: Drew Ansbacher <drew.ansbacher@spiredigital.com>

* improve server settings inputs, still needs work

* delete all notifications, styling, and bugs

* contracted by default

Co-authored-by: Drew Ansbacher <drew.ansbacher@gmail.com>
Co-authored-by: Drew Ansbacher <drew.ansbacher@spiredigital.com>
This commit is contained in:
Matt Hill
2021-08-06 09:25:20 -06:00
committed by Aiden McClelland
parent a43ff976a2
commit 5741cf084f
117 changed files with 1967 additions and 1271 deletions

View File

@@ -2,16 +2,17 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { AppActionInputPage } from './app-action-input.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'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { FormObjectComponentModule } from 'src/app/components/form-object/form-object.component.module'
@NgModule({
declarations: [AppActionInputPage],
imports: [
CommonModule,
IonicModule,
ObjectConfigComponentModule,
ConfigHeaderComponentModule,
FormsModule,
ReactiveFormsModule,
FormObjectComponentModule,
],
entryComponents: [AppActionInputPage],
exports: [AppActionInputPage],

View File

@@ -1,27 +1,29 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="dismiss()" color="light">
<ion-icon slot="icon-only" name="close-outline"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>{{ action.name }}</ion-title>
<ion-buttons slot="end">
<ion-button [disabled]="error" (click)="save()">
Save
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<config-header [spec]="spec" [error]="error"></config-header>
<!-- object -->
<ion-item-group>
<ion-item-divider></ion-item-divider>
<object-config [cursor]="cursor" (onEdit)="handleObjectEdit()"></object-config>
</ion-item-group>
<ion-content class="ion-padding">
<form [formGroup]="actionForm" (ngSubmit)="save()" novalidate>
<form-object
[objectSpec]="action['input-spec']"
[formGroup]="actionForm"
></form-object>
</form>
</ion-content>
<ion-footer>
<ion-toolbar>
<ion-buttons slot="start" class="ion-padding-start">
<ion-button fill="outline" (click)="dismiss()">
Cancel
</ion-button>
</ion-buttons>
<ion-buttons slot="end" class="ion-padding-end">
<ion-button fill="outline" color="primary" (click)="save()">
Execute
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-footer>

View File

@@ -0,0 +1,9 @@
button:disabled,
button[disabled]{
border: 1px solid #999999;
background-color: #cccccc;
color: #666666;
}
button {
color: var(--ion-color-primary);
}

View File

@@ -1,9 +1,8 @@
import { Component, Input } from '@angular/core'
import { LoadingController, ModalController } from '@ionic/angular'
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
import { ValueSpecObject } from 'src/app/pkg-config/config-types'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { FormGroup } from '@angular/forms'
import { ModalController } from '@ionic/angular'
import { Action } from 'src/app/services/patch-db/data-model'
import { FormService } from 'src/app/services/form.service'
@Component({
selector: 'app-action-input',
@@ -12,22 +11,15 @@ import { Action } from 'src/app/services/patch-db/data-model'
})
export class AppActionInputPage {
@Input() action: Action
@Input() cursor: ConfigCursor<'object'>
@Input() execute: () => Promise<void>
spec: ValueSpecObject
value: object
error: string
actionForm: FormGroup
constructor (
private readonly modalCtrl: ModalController,
private readonly errToast: ErrorToastService,
private readonly loadingCtrl: LoadingController,
private readonly formService: FormService,
) { }
ngOnInit () {
this.spec = this.cursor.spec()
this.value = this.cursor.config()
this.error = this.cursor.checkInvalid()
this.actionForm = this.formService.createForm(this.action['input-spec'])
}
async dismiss (): Promise<void> {
@@ -35,24 +27,15 @@ export class AppActionInputPage {
}
async save (): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Executing action',
cssClass: 'loader-ontop-of-all',
})
await loader.present()
try {
await this.execute()
this.modalCtrl.dismiss()
} catch (e) {
this.errToast.present(e)
} finally {
loader.dismiss()
if (this.actionForm.invalid) {
this.actionForm.markAllAsTouched()
document.getElementsByClassName('validation-error')[0].parentElement.parentElement.scrollIntoView({ behavior: 'smooth' })
return
}
this.modalCtrl.dismiss(this.actionForm.value)
}
handleObjectEdit (): void {
this.error = this.cursor.checkInvalid()
asIsOrder () {
return 0
}
}

View File

@@ -1,23 +1,11 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="dismiss()">
<ion-icon name="arrow-back"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>{{ spec.name }}</ion-title>
<ion-buttons *ngIf="spec.subtype !== 'enum'" slot="end">
<ion-button (click)="presentModalValueEdit()">
<ion-icon name="add" color="primary"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<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>
@@ -46,7 +34,7 @@
</ion-item-divider>
<div *ngFor="let v of value; index as i;">
<ion-item button detail="false" (click)="presentModalValueEdit(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>
@@ -55,7 +43,7 @@
<ion-label>{{ valueString[i] }}</ion-label>
<ion-button slot="end" fill="clear" (click)="presentAlertDelete(i, $event)">
<ion-icon slot="icon-only" name="close-outline"></ion-icon>
<ion-icon slot="icon-only" name="close"></ion-icon>
</ion-button>
</ion-item>
</div>

View File

@@ -1,17 +1,16 @@
import { Component, Input } from '@angular/core'
import { AlertController } from '@ionic/angular'
import { AlertController, IonNav } from '@ionic/angular'
import { Annotations, Range } from '../../pkg-config/config-utilities'
import { TrackingModalController } from 'src/app/services/tracking-modal-controller.service'
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
import { ValueSpecList, isValueSpecListOf } from 'src/app/pkg-config/config-types'
import { ModalPresentable } from 'src/app/pkg-config/modal-presentable'
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 extends ModalPresentable {
export class AppConfigListPage {
@Input() cursor: ConfigCursor<'list'>
spec: ValueSpecList
@@ -22,7 +21,6 @@ export class AppConfigListPage extends ModalPresentable {
// enum only
options: { value: string, checked: boolean }[] = []
selectAll = true
//
min: number | undefined
max: number | undefined
@@ -34,10 +32,9 @@ export class AppConfigListPage extends ModalPresentable {
constructor (
private readonly alertCtrl: AlertController,
trackingModalCtrl: TrackingModalController,
) {
super(trackingModalCtrl)
}
private readonly subNav: SubNavService,
private readonly nav: IonNav,
) { }
ngOnInit () {
this.spec = this.cursor.spec()
@@ -59,10 +56,6 @@ export class AppConfigListPage extends ModalPresentable {
this.updateCaches()
}
async dismiss () {
return this.dismissModal(this.value)
}
// enum only
toggleSelectAll () {
if (!isValueSpecListOf(this.spec, 'enum')) { throw new Error('unreachable') }
@@ -98,10 +91,10 @@ export class AppConfigListPage extends ModalPresentable {
this.updateCaches()
}
async presentModalValueEdit (index?: number) {
async createOrEdit (index?: number) {
const nextCursor = this.cursor.seekNext(index === undefined ? this.value.length : index)
nextCursor.createFirstEntryForList()
return this.presentModal(nextCursor, () => this.updateCaches())
this.subNav.push(String(index), nextCursor, this.nav)
}
async presentAlertDelete (key: number, e: Event) {

View File

@@ -1,27 +1,4 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="dismiss()">
<ion-icon name="arrow-back"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>{{ spec.name }}</ion-title>
<ion-buttons *ngIf="spec.nullable" slot="end">
<ion-button color="danger" (click)="presentAlertDestroy()">
Delete
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-content class="subheader-padding">
<config-header [spec]="spec" [error]="error"></config-header>
<!-- object -->
<ion-item-group>
<ion-item-divider></ion-item-divider>
<object-config [cursor]="cursor" (onEdit)="handleObjectEdit()"></object-config>
</ion-item-group>
<object-config [cursor]="cursor" (onEdit)="handleObjectEdit()" (onClick)="updatePath($event)"></object-config>
</ion-content>

View File

@@ -1,15 +1,4 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="dismiss()">
<ion-icon name="arrow-back"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>{{ spec.name }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-content class="subheader-padding">
<config-header [spec]="spec" [error]="error"></config-header>
@@ -24,10 +13,10 @@
</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.variantNames[value[spec.tag.id]]"
[(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.variantNames[option.key] }}
{{ spec.tag['variant-names'][option.key] }}
<span *ngIf="option.key === spec.default"> (default)</span>
</ion-select-option>
</ion-select>

View File

@@ -46,8 +46,8 @@ export class AppConfigUnionPage {
setSelectOptions () {
return {
header: this.spec.tag.name,
subHeader: this.spec.changeWarning ? 'Warning!' : undefined,
message: this.spec.changeWarning ? `${this.spec.changeWarning}` : undefined,
subHeader: this.spec['change-warning'] ? 'Warning!' : undefined,
message: this.spec['change-warning'] ? `${this.spec['change-warning']}` : undefined,
cssClass: 'select-change-warning',
}
}

View File

@@ -1,22 +1,4 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="dismiss()">
<ion-icon name="arrow-back"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>
{{ spec.name }}
</ion-title>
<ion-buttons *ngIf="!!saveFn" slot="end">
<ion-button [disabled]="!!error" (click)="save()">
Save
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-content class="subheader-padding">
<config-header [spec]="spec" [error]="error"></config-header>
@@ -55,15 +37,15 @@
<ion-list *ngIf="spec.type === 'enum'">
<ion-radio-group [(ngModel)]="value">
<ion-item *ngFor="let option of spec.values">
<ion-label>{{ spec.valueNames[option] }}</ion-label>
<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.patternDescription">
{{ spec.patternDescription }}
<p *ngIf="spec.type === 'string' && spec['pattern-description']">
{{ spec['pattern-description'] }}
</p>
<p *ngIf="spec.type === 'number' && spec.integral">
{{ integralDescription }}

View File

@@ -127,7 +127,7 @@ export class AppConfigValuePage {
}
// test pattern if string
if (this.spec.type === 'string' && this.value) {
const { pattern, patternDescription } = this.spec
const { pattern, 'pattern-description' : patternDescription } = this.spec
if (pattern && !RegExp(pattern).test(this.value as string)) {
this.error = patternDescription || `Must match ${pattern}`
return false

View File

@@ -0,0 +1,22 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { IonicModule } from '@ionic/angular'
import { AppConfigPage } from './app-config.page'
import { SharingModule } from 'src/app/modules/sharing.module'
import { FormObjectComponentModule } from 'src/app/components/form-object/form-object.component.module'
@NgModule({
declarations: [AppConfigPage],
imports: [
CommonModule,
FormsModule,
IonicModule,
SharingModule,
FormObjectComponentModule,
ReactiveFormsModule,
],
entryComponents: [AppConfigPage],
exports: [AppConfigPage],
})
export class AppConfigPageModule { }

View File

@@ -0,0 +1,94 @@
<ion-header>
<ion-toolbar *ngIf="patch.data['package-data'][pkgId] as pkg">
<ion-title>Config</ion-title>
<ion-buttons slot="end" class="ion-padding-end">
<ion-button fill="clear" [disabled]="loadingText" (click)="resetDefaults()">
Reset Defaults
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<!-- loading -->
<text-spinner *ngIf="loadingText; else loaded" [text]="loadingText"></text-spinner>
<!-- not loading -->
<ng-template #loaded>
<ng-container *ngIf="patch.data['package-data'][pkgId] as pkg">
<ng-container *ngIf="pkg.manifest.config && !pkg.installed.status.configured && !edited">
<ion-item class="notifier-item">
<ion-label class="ion-text-wrap">
<h2 style="display: flex; align-items: center; margin-bottom: 3px;">
<ion-icon size="small" style="margin-right: 5px" slot="start" color="dark" slot="start" name="alert-circle-outline"></ion-icon>
<ion-text style="font-size: smaller;">Initial Config</ion-text>
</h2>
<p style="font-size: small">To use the default config for {{ pkg.manifest.title }}, click "Save" above.</p>
</ion-label>
</ion-item>
</ng-container>
<ng-container *ngIf="rec && showRec">
<ion-item class="rec-item">
<ion-label class="ion-text-wrap">
<h2 style="display: flex; align-items: center;">
<ion-icon size="small" style="margin: 4px" slot="start" color="primary" slot="start" name="ellipse"></ion-icon>
<ion-thumbnail style="width: 3vh; height: 3vh; margin: 0px 2px 0px 5px;" slot="start">
<img [src]="rec.dependentIcon" [alt]="rec.dependentTitle"/>
</ion-thumbnail>
<ion-text style="margin: 5px; font-family: 'Montserrat'; font-size: smaller;">{{ rec.dependentTitle }}</ion-text>
</h2>
<div style="margin: 7px 5px;">
<p style="font-size: small; color: var(--ion-color-medium)"> {{ pkg.manifest.title }} config has been modified to satisfy {{ rec.dependentTitle }}.
<ion-text color="dark">To accept the changes, click “Save” above.</ion-text>
</p>
<a style="font-size: small" *ngIf="!openRec" (click)="openRec = true">More Info</a>
<ng-container *ngIf="openRec">
<p style="margin-top: 10px; color: var(--ion-color-medium); font-size: small" [innerHTML]="rec.description"></p>
<a style="font-size: x-small; font-style: italic;" (click)="openRec = false">hide</a>
</ng-container>
<ion-button style="position: absolute; right: 0; top: 0" color="primary" fill="clear" (click)="dismissRec()">
<ion-icon name="close"></ion-icon>
</ion-button>
</div>
</ion-label>
</ion-item>
<ion-item-divider></ion-item-divider>
</ng-container>
<!-- no config -->
<ion-item *ngIf="!hasConfig">
<ion-label class="ion-text-wrap">
<p>No config options for {{ pkg.manifest.title }} {{ pkg.manifest.version }}.</p>
</ion-label>
</ion-item>
</ng-container>
<!-- has config -->
<form [formGroup]="configForm" (ngSubmit)="save()" novalidate>
<form-object
[objectSpec]="configSpec"
[formGroup]="configForm"
[current]="current"
showEdited
></form-object>
</form>
</ng-template>
</ion-content>
<ion-footer>
<ion-toolbar *ngIf="patch.data['package-data'][pkgId] as pkg">
<ion-buttons slot="start" class="ion-padding-start">
<ion-button fill="outline" (click)="dismiss()">
Cancel
</ion-button>
</ion-buttons>
<ion-buttons slot="end" class="ion-padding-end">
<ion-button fill="outline" color="primary" [disabled]="loadingText" (click)="save(pkg)">
Save
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-footer>

View File

@@ -0,0 +1,8 @@
.notifier-item {
margin: 12px;
margin-top: 0px;
border-radius: 12px;
// kills the lines
--border-width: 0;
--inner-border-width: 0;
}

View File

@@ -0,0 +1,181 @@
import { Component, Input, ViewChild } from '@angular/core'
import { AlertController, ModalController, IonContent, LoadingController } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy/embassy-api.service'
import { isEmptyObject, Recommendation } from 'src/app/util/misc.util'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { ConfigSpec } from 'src/app/pkg-config/config-types'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { FormGroup } from '@angular/forms'
import { FormService } from 'src/app/services/form.service'
@Component({
selector: 'app-config',
templateUrl: './app-config.page.html',
styleUrls: ['./app-config.page.scss'],
})
export class AppConfigPage {
@Input() pkgId: string
loadingText: string | undefined
configSpec: ConfigSpec
configForm: FormGroup
current: object
hasConfig = false
rec: Recommendation | null = null
showRec = true
openRec = false
@ViewChild(IonContent) content: IonContent
constructor (
private readonly wizardBaker: WizardBaker,
private readonly embassyApi: ApiService,
private readonly errToast: ErrorToastService,
private readonly loadingCtrl: LoadingController,
private readonly alertCtrl: AlertController,
private readonly modalCtrl: ModalController,
private readonly formService: FormService,
public readonly patch: PatchDbService,
) { }
async ngOnInit () {
const rec = history.state?.configRecommendation as Recommendation
try {
this.loadingText = 'Loading Config'
const { spec, config } = await this.embassyApi.getPackageConfig({ id: this.pkgId })
let depConfig: object
if (rec) {
this.loadingText = `Setting properties to accommodate ${rec.dependentTitle}...`
depConfig = await this.embassyApi.dryConfigureDependency({ 'dependency-id': this.pkgId, 'dependent-id': rec.dependentId })
}
this.setConfig(spec, config, depConfig)
} catch (e) {
this.errToast.present(e)
} finally {
this.loadingText = undefined
}
}
ngAfterViewInit () {
this.content.scrollToPoint(undefined, 1)
}
setConfig (spec: ConfigSpec, config: object, depConfig?: object) {
this.configSpec = spec
this.current = config
this.hasConfig = !isEmptyObject(config)
this.configForm = this.formService.createForm(spec, { ...config, ...depConfig })
this.configForm.markAllAsTouched()
if (depConfig) {
this.markDirtyRecursive(this.configForm, depConfig)
}
}
markDirtyRecursive (group: FormGroup, config: object) {
Object.keys(config).forEach(key => {
const next = group.get(key)
if (!next) throw new Error('Dependency config not compatible with service version. Please contact support')
const newVal = config[key]
// check if val is an object
if (newVal && typeof newVal === 'object' && !Array.isArray(newVal)) {
this.markDirtyRecursive(next as FormGroup, newVal)
} else {
let val1 = group.get(key).value
let val2 = config[key]
if (Array.isArray(newVal)) {
val1 = JSON.stringify(val1)
val2 = JSON.stringify(val2)
}
if (val1 != val2) next.markAsDirty()
}
})
}
resetDefaults () {
this.configForm = this.formService.createForm(this.configSpec)
this.markDirtyRecursive(this.configForm, this.current)
}
dismissRec () {
this.showRec = false
}
async dismiss () {
if (this.configForm.dirty) {
await this.presentAlertUnsaved()
} else {
this.modalCtrl.dismiss()
}
}
async save (pkg: PackageDataEntry) {
if (this.configForm.invalid) {
document.getElementsByClassName('validation-error')[0].parentElement.parentElement.scrollIntoView({ behavior: 'smooth' })
return
}
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: `Saving config...`,
cssClass: 'loader',
})
await loader.present()
const config = this.configForm.value
try {
const breakages = await this.embassyApi.drySetPackageConfig({
id: pkg.manifest.id,
config,
})
if (!isEmptyObject(breakages.length)) {
const { cancelled } = await wizardModal(
this.modalCtrl,
this.wizardBaker.configure({
pkg,
breakages,
}),
)
if (cancelled) return
}
await this.embassyApi.setPackageConfig({
id: pkg.manifest.id,
config,
})
this.modalCtrl.dismiss()
} catch (e) {
this.errToast.present(e)
} finally {
loader.dismiss()
}
}
private async presentAlertUnsaved () {
const alert = await this.alertCtrl.create({
header: 'Unsaved Changes',
message: 'You have unsaved changes. Are you sure you want to leave?',
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: `Leave`,
handler: () => {
this.modalCtrl.dismiss()
},
},
],
})
await alert.present()
}
}

View File

@@ -10,6 +10,7 @@
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<text-spinner *ngIf="loading" text="Loading Drives"></text-spinner>

View File

@@ -2,19 +2,15 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { AppRestoreComponent } from './app-restore.component'
import { PwaBackComponentModule } from '../../components/pwa-back-button/pwa-back.component.module'
import { BackupConfirmationComponentModule } from '../backup-confirmation/backup-confirmation.component.module'
import { SharingModule } from '../../modules/sharing.module'
import { TextSpinnerComponentModule } from '../../components/text-spinner/text-spinner.component.module'
@NgModule({
imports: [
CommonModule,
IonicModule,
SharingModule,
BackupConfirmationComponentModule,
PwaBackComponentModule,
TextSpinnerComponentModule,
SharingModule,
],
declarations: [
AppRestoreComponent,

View File

@@ -31,7 +31,6 @@ export class AppRestoreComponent {
) { }
ngOnInit () {
console.log('initing')
this.getExternalDisks()
}
@@ -51,7 +50,6 @@ export class AppRestoreComponent {
} catch (e) {
this.errToast.present(e)
} finally {
console.log('loading false')
this.loading = false
}
}
@@ -80,7 +78,6 @@ export class AppRestoreComponent {
}
private async restore (logicalname: string, password: string): Promise<void> {
console.log('here here here')
this.submitting = true
// await loader.present()

View File

@@ -0,0 +1,17 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { EnumListPage } from './enum-list.page'
import { FormsModule } from '@angular/forms'
@NgModule({
declarations: [EnumListPage],
imports: [
CommonModule,
IonicModule,
FormsModule,
],
entryComponents: [EnumListPage],
exports: [EnumListPage],
})
export class EnumListPageModule { }

View File

@@ -0,0 +1,36 @@
<ion-header>
<ion-toolbar>
<ion-title>
{{ spec.name }}
</ion-title>
<ion-buttons slot="end">
<ion-button slot="end" fill="clear" color="primary" (click)="toggleSelectAll()">
{{ selectAll ? 'All' : 'None' }}
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-item-group>
<ion-item *ngFor="let option of options | keyvalue : asIsOrder">
<ion-label>{{ option.key }}</ion-label>
<ion-checkbox slot="end" [(ngModel)]="option.value" (click)="toggleSelected(option.key)"></ion-checkbox>
</ion-item>
</ion-item-group>
</ion-content>
<ion-footer>
<ion-toolbar>
<ion-buttons slot="start" class="ion-padding-start">
<ion-button fill="outline" (click)="dismiss()">
Cancel
</ion-button>
</ion-buttons>
<ion-buttons slot="end" class="ion-padding-end">
<ion-button fill="outline" color="primary" (click)="save()">
Done
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-footer>

View File

@@ -0,0 +1,56 @@
import { Component, Input } from '@angular/core'
import { ModalController } from '@ionic/angular'
import { ValueSpecListOf } from '../../pkg-config/config-types'
// import { Range } from '../../pkg-config/config-utilities'
@Component({
selector: 'enum-list',
templateUrl: './enum-list.page.html',
styleUrls: ['./enum-list.page.scss'],
})
export class EnumListPage {
@Input() key: string
@Input() spec: ValueSpecListOf<'enum'>
@Input() current: string[]
options: { [option: string]: boolean } = { }
// min: number | undefined
// max: number | undefined
// minMessage: string
// maxMessage: string
selectAll = true
constructor (
private readonly modalCtrl: ModalController,
) { }
ngOnInit () {
// const range = Range.from(this.spec.range)
// this.min = range.integralMin()
// this.max = range.integralMax()
// this.minMessage = `The minimum number of ${this.key} is ${this.min}.`
// this.maxMessage = `The maximum number of ${this.key} is ${this.max}.`
for (let val of this.spec.spec.values) {
this.options[val] = this.current.includes(val)
}
}
dismiss () {
this.modalCtrl.dismiss()
}
save () {
this.modalCtrl.dismiss(Object.keys(this.options).filter(key => this.options[key]))
}
toggleSelectAll () {
Object.keys(this.options).forEach(k => this.options[k] = this.selectAll)
this.selectAll = !this.selectAll
}
async toggleSelected (key: string) {
this.options[key] = !this.options[key]
}
}

View File

@@ -3,14 +3,12 @@ import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { MarkdownPage } from './markdown.page'
import { SharingModule } from 'src/app/modules/sharing.module'
import { TextSpinnerComponentModule } from 'src/app/components/text-spinner/text-spinner.component.module'
@NgModule({
imports: [
CommonModule,
IonicModule,
SharingModule,
TextSpinnerComponentModule,
],
declarations: [MarkdownPage],
})

View File

@@ -2,7 +2,7 @@
<ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="dismiss()">
<ion-icon slot="icon-only" name="close-outline"></ion-icon>
<ion-icon slot="icon-only" name="close"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>{{ title | titlecase }}</ion-title>