finish file upload API and implement for config

This commit is contained in:
Matt Hill
2023-03-21 16:04:16 -06:00
committed by Aiden McClelland
parent dacd5d3e6b
commit aa950669f6
8 changed files with 125 additions and 70 deletions

View File

@@ -6,7 +6,6 @@ import { SharedPipesModule } from '@start9labs/shared'
import { TuiElasticContainerModule } from '@taiga-ui/kit' import { TuiElasticContainerModule } from '@taiga-ui/kit'
import { TuiExpandModule } from '@taiga-ui/core' import { TuiExpandModule } from '@taiga-ui/core'
import { EnumListPageModule } from 'src/app/modals/enum-list/enum-list.module' import { EnumListPageModule } from 'src/app/modals/enum-list/enum-list.module'
import { FormLabelComponent } from './form-label/form-label.component' import { FormLabelComponent } from './form-label/form-label.component'
import { FormObjectComponent } from './form-object/form-object.component' import { FormObjectComponent } from './form-object/form-object.component'
import { FormUnionComponent } from './form-union/form-union.component' import { FormUnionComponent } from './form-union/form-union.component'
@@ -14,7 +13,6 @@ import {
GetErrorPipe, GetErrorPipe,
ToWarningTextPipe, ToWarningTextPipe,
ToElementIdPipe, ToElementIdPipe,
GetControlPipe,
ToEnumListDisplayPipe, ToEnumListDisplayPipe,
ToRangePipe, ToRangePipe,
} from './form-object.pipes' } from './form-object.pipes'
@@ -28,7 +26,6 @@ import {
GetErrorPipe, GetErrorPipe,
ToEnumListDisplayPipe, ToEnumListDisplayPipe,
ToElementIdPipe, ToElementIdPipe,
GetControlPipe,
ToRangePipe, ToRangePipe,
], ],
imports: [ imports: [

View File

@@ -74,19 +74,3 @@ export class ToElementIdPipe implements PipeTransform {
return getElementId(objectId, key, index) return getElementId(objectId, key, index)
} }
} }
@Pipe({
name: 'getControl',
})
export class GetControlPipe implements PipeTransform {
transform(
formGroup: FormGroup,
key: string,
index?: number,
): AbstractControl {
const abstractControl = formGroup.get(key)!
if (index !== undefined)
return (abstractControl as UntypedFormArray).at(index)
return abstractControl
}
}

View File

@@ -63,18 +63,14 @@
</ion-note> </ion-note>
</ion-item> </ion-item>
<p class="error-message"> <p class="error-message">
<span *ngIf="(formGroup | getControl : entry.key).errors as errors"> <span *ngIf="formGroup.get(entry.key)?.errors as errors">
{{ errors | getError : $any(spec)['pattern-description'] }} {{ errors | getError : $any(spec)['pattern-description'] }}
</span> </span>
</p> </p>
</ng-container> </ng-container>
<!-- boolean, enum, or file --> <!-- boolean or enum -->
<ion-item <ion-item
*ngIf=" *ngIf="spec.type === 'boolean' || spec.type === 'enum'"
spec.type === 'boolean' ||
spec.type === 'enum' ||
spec.type === 'file'
"
style="--padding-start: 0" style="--padding-start: 0"
> >
<ion-button <ion-button
@@ -130,36 +126,65 @@
{{ spec['value-names'][option] }} {{ spec['value-names'][option] }}
</ion-select-option> </ion-select-option>
</ion-select> </ion-select>
</ion-item>
<div <!-- file -->
*ngIf="spec.type === 'file' && formGroup.get(entry.key) as control" <ng-container
slot="end" *ngIf="spec.type === 'file' && formGroup.get(entry.key) as control"
> >
<ion-item style="--padding-start: 0">
<ion-button <ion-button
*ngIf="!control.value; else hasFile" *ngIf="spec.description as desc"
strong fill="clear"
color="dark" (click.stop)="presentAlertDescription(spec.name, desc)"
size="small" style="--padding-start: 0"
> >
<label for="upload-file">Browse...</label> <ion-icon
name="help-circle-outline"
slot="icon-only"
size="small"
></ion-icon>
</ion-button>
<ion-label>
<b>
{{ spec.name }}
<ion-text *ngIf="entry.value.dirty" color="warning">
(Edited)
</ion-text>
</b>
</ion-label>
<div slot="end">
<ion-button
*ngIf="!control.value; else hasFile"
strong
color="dark"
size="small"
(click)="uploadFile.click()"
>
Browse...
</ion-button>
<input <input
type="file" type="file"
[accept]="spec.extensions.join(',')" [accept]="spec.extensions.join(',')"
style="position: absolute; opacity: 0; height: 100%" style="display: none"
id="upload-file" #uploadFile
(change)="handleFileInput($event, control)" (change)="handleFileInput($event, control)"
/> />
</ion-button> <ng-template #hasFile>
<ng-template #hasFile> <div class="inline">
<div class="inline"> <p class="ion-padding-end">{{ control.value.name }}</p>
<p class="ion-padding-end">{{ control.value.name }}</p> <div style="cursor: pointer" (click)="clearFile(control)">
<div style="cursor: pointer" (click)="clearFile(control)"> <ion-icon name="close"></ion-icon>
<ion-icon name="close"></ion-icon> </div>
</div> </div>
</div> </ng-template>
</ng-template> </div>
</div> </ion-item>
</ion-item> <p class="error-message">
<span *ngIf="control.errors as errors">
{{ errors | getError }}
</span>
</p>
</ng-container>
<!-- object --> <!-- object -->
<ng-container *ngIf="spec.type === 'object'"> <ng-container *ngIf="spec.type === 'object'">
<!-- label --> <!-- label -->
@@ -242,7 +267,7 @@
</ion-button> </ion-button>
</ion-item-divider> </ion-item-divider>
<p class="error-message" style="margin-bottom: 8px"> <p class="error-message" style="margin-bottom: 8px">
<span *ngIf="(formGroup | getControl : entry.key).errors as errors"> <span *ngIf="formGroup.get(entry.key)?.errors as errors">
{{ errors | getError }} {{ errors | getError }}
</span> </span>
</p> </p>
@@ -353,7 +378,7 @@
<p class="error-message"> <p class="error-message">
<span <span
*ngIf=" *ngIf="
(formGroup | getControl : entry.key : i).errors as errors $any(formGroup.get(entry.key))?.at(i)?.errors as errors
" "
> >
{{ errors | getError : $any(spec)['pattern-description'] }} {{ errors | getError : $any(spec)['pattern-description'] }}
@@ -396,7 +421,7 @@
</ion-button> </ion-button>
</ion-item> </ion-item>
<p class="error-message"> <p class="error-message">
<span *ngIf="(formGroup | getControl : entry.key).errors as errors"> <span *ngIf="formGroup.get(entry.key)?.errors as errors">
{{ errors | getError }} {{ errors | getError }}
</span> </span>
</p> </p>

View File

@@ -17,7 +17,6 @@ import { InputSpec } from 'start-sdk/types/config-types'
import { import {
DataModel, DataModel,
PackageDataEntry, PackageDataEntry,
PackageState,
} from 'src/app/services/patch-db/data-model' } from 'src/app/services/patch-db/data-model'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { UntypedFormGroup } from '@angular/forms' import { UntypedFormGroup } from '@angular/forms'
@@ -139,32 +138,64 @@ export class AppConfigPage {
this.saving = true this.saving = true
const config = this.configForm!.value
const fileKeys = Object.keys(config).filter(
key => config[key] instanceof File,
)
let loader: HTMLIonLoadingElement | undefined
if (fileKeys.length) {
loader = await this.loadingCtrl.create({
message: `Uploading File${fileKeys.length > 1 ? 's' : ''}...`,
})
await loader.present()
try {
const hashes = await Promise.all(
fileKeys.map(key => this.embassyApi.uploadFile(config[key])),
)
fileKeys.forEach((key, i) => (config[key] = hashes[i]))
} catch (e: any) {
this.errToast.present(e)
} finally {
await loader.dismiss()
return
}
}
if (await hasCurrentDeps(this.patch, this.pkgId)) { if (await hasCurrentDeps(this.patch, this.pkgId)) {
this.dryConfigure() this.dryConfigure(config, loader)
} else { } else {
this.configure() this.configure(config, loader)
} }
} }
private async dryConfigure() { private async dryConfigure(
const loader = await this.loadingCtrl.create({ config: Record<string, any>,
message: 'Checking dependent services...', loader?: HTMLIonLoadingElement,
}) ) {
await loader.present() const message = 'Checking dependent services...'
if (loader) {
loader.message = message
} else {
loader = await this.loadingCtrl.create({ message })
await loader.present()
}
try { try {
const breakages = await this.embassyApi.drySetPackageConfig({ const breakages = await this.embassyApi.drySetPackageConfig({
id: this.pkgId, id: this.pkgId,
config: this.configForm!.value, config,
}) })
if (isEmptyObject(breakages)) { if (isEmptyObject(breakages)) {
this.configure(loader) this.configure(config, loader)
} else { } else {
await loader.dismiss() await loader.dismiss()
const proceed = await this.presentAlertBreakages(breakages) const proceed = await this.presentAlertBreakages(breakages)
if (proceed) { if (proceed) {
this.configure() this.configure(config)
} else { } else {
this.saving = false this.saving = false
} }
@@ -176,7 +207,10 @@ export class AppConfigPage {
} }
} }
private async configure(loader?: HTMLIonLoadingElement) { private async configure(
config: Record<string, any>,
loader?: HTMLIonLoadingElement,
) {
const message = 'Saving...' const message = 'Saving...'
if (loader) { if (loader) {
loader.message = message loader.message = message
@@ -188,7 +222,7 @@ export class AppConfigPage {
try { try {
await this.embassyApi.setPackageConfig({ await this.embassyApi.setPackageConfig({
id: this.pkgId, id: this.pkgId,
config: this.configForm!.value, config,
}) })
this.modalCtrl.dismiss() this.modalCtrl.dismiss()
} catch (e: any) { } catch (e: any) {

View File

@@ -847,7 +847,7 @@ export module Mock {
name: 'Needed File', name: 'Needed File',
type: 'file', type: 'file',
description: 'A file we need', description: 'A file we need',
placeholder: 'Testing placeholder', placeholder: null, // @TODO delete
warning: 'Testing warning', warning: 'Testing warning',
nullable: false, nullable: false,
extensions: ['.png'], extensions: ['.png'],

View File

@@ -30,7 +30,9 @@ export abstract class ApiService {
abstract getStatic(url: string): Promise<string> abstract getStatic(url: string): Promise<string>
// for sideloading packages // for sideloading packages
abstract uploadPackage(guid: string, body: Blob): Promise<string> abstract uploadPackage(guid: string, body: Blob): Promise<void>
abstract uploadFile(body: Blob): Promise<string>
// db // db

View File

@@ -31,7 +31,7 @@ export class LiveApiService extends ApiService {
private readonly patch: PatchDB<DataModel>, private readonly patch: PatchDB<DataModel>,
) { ) {
super() super()
; (window as any).rpcClient = this ;(window as any).rpcClient = this
} }
// for getting static files: ex icons, instructions, licenses // for getting static files: ex icons, instructions, licenses
@@ -44,7 +44,7 @@ export class LiveApiService extends ApiService {
} }
// for sideloading packages // for sideloading packages
async uploadPackage(guid: string, body: Blob): Promise<string> { async uploadPackage(guid: string, body: Blob): Promise<void> {
return this.httpRequest({ return this.httpRequest({
method: Method.POST, method: Method.POST,
body, body,
@@ -53,6 +53,15 @@ export class LiveApiService extends ApiService {
}) })
} }
async uploadFile(body: Blob): Promise<string> {
return this.httpRequest({
method: Method.POST,
body,
url: `/rest/upload`,
responseType: 'text',
})
}
// db // db
async setDbValue<T>( async setDbValue<T>(

View File

@@ -86,9 +86,13 @@ export class MockApiService extends ApiService {
return markdown return markdown
} }
async uploadPackage(guid: string, body: Blob): Promise<string> { async uploadPackage(guid: string, body: Blob): Promise<void> {
await pauseFor(2000) await pauseFor(2000)
return 'success' }
async uploadFile(body: Blob): Promise<string> {
await pauseFor(2000)
return 'returnedhash'
} }
// db // db