mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 14:29:45 +00:00
finish file upload API and implement for config
This commit is contained in:
committed by
Aiden McClelland
parent
dacd5d3e6b
commit
aa950669f6
@@ -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: [
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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>(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user