mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
ui: concatObValues + comments
This commit is contained in:
committed by
Aiden McClelland
parent
8ce43d808e
commit
397236c68e
@@ -109,9 +109,9 @@
|
||||
<p>
|
||||
<a (click)="copyLan()">{{ vars.lanAddress }} <ion-icon name="copy-outline" class="tiny-icon"></ion-icon></a>
|
||||
</p>
|
||||
<p *ngIf="vars.testingLanConnection" style="display: flex; align-items: center; font-size: x-small">
|
||||
<ion-text color="dark">Testing Connection</ion-text>
|
||||
<ion-spinner style="height: 15px; margin-left: 5px;" name="dots" color="dark"></ion-spinner>
|
||||
<p *ngIf="vars.testingLanConnection" style="display: flex; align-items: center; font-size: x-small; margin-top: 2px">
|
||||
<ion-text color="warning">Testing Connection</ion-text>
|
||||
<ion-spinner style="height: 15px; margin-left: 5px;" name="dots" color="warning"></ion-spinner>
|
||||
</p>
|
||||
</ion-label>
|
||||
<ion-toggle (ionChange)="$lanToggled$.next($event)" [checked]="vars.lanEnabled" slot="end" class="lan-toggle" [disabled]="vars.status !== 'RUNNING' || vars.testingLanConnection"></ion-toggle>
|
||||
|
||||
@@ -66,3 +66,9 @@
|
||||
--handle-width: 0.9em;
|
||||
--handle-height: 0.9em;
|
||||
}
|
||||
|
||||
.item-interactive-disabled:not(.item-multiple-inputs) ion-label {
|
||||
cursor: default;
|
||||
opacity: 1 !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
@@ -13,13 +13,13 @@ import { LoaderService, markAsLoadingDuring$, markAsLoadingDuringP } from 'src/a
|
||||
import { BehaviorSubject, combineLatest, from, merge, Observable, of, Subject } from 'rxjs'
|
||||
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||
import { catchError, concatMap, delay, distinctUntilChanged, filter, map, retryWhen, switchMap, take, tap } from 'rxjs/operators'
|
||||
import { catchError, concatMap, delay, distinctUntilChanged, filter, map, mergeMap, retryWhen, switchMap, take, tap } from 'rxjs/operators'
|
||||
import { Cleanup } from 'src/app/util/cleanup'
|
||||
import { InformationPopoverComponent } from 'src/app/components/information-popover/information-popover.component'
|
||||
import { Emver } from 'src/app/services/emver.service'
|
||||
import { displayEmver } from 'src/app/pipes/emver.pipe'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { squash } from 'src/app/util/rxjs.util'
|
||||
import { concatObservableValues, squash } from 'src/app/util/rxjs.util'
|
||||
@Component({
|
||||
selector: 'app-installed-show',
|
||||
templateUrl: './app-installed-show.page.html',
|
||||
@@ -79,36 +79,28 @@ export class AppInstalledShowPage extends Cleanup {
|
||||
concatMap(app =>
|
||||
merge(
|
||||
this.syncWhenDependencyInstalls(),
|
||||
// new lan info from sync daemon
|
||||
combineLatest([app.lanEnabled, this.$lanConnected$, app.status, this.$testingLanConnection$]).pipe(
|
||||
filter(([_, __, s, alreadyConnecting]) => s === AppStatus.RUNNING && !alreadyConnecting),
|
||||
concatMap(([enabled, connected]) => {
|
||||
// console.log('enabled', enabled)
|
||||
// console.log('connected', connected)
|
||||
// new lan info or status info from sync daemon
|
||||
combineLatest([app.lanEnabled, app.status]).pipe(
|
||||
concatObservableValues<boolean, AppStatus, boolean, boolean>([this.$lanConnected$, this.$testingLanConnection$]),
|
||||
concatMap(([enabled, status, connected, alreadyConnecting]) => {
|
||||
if (status !== AppStatus.RUNNING) return of(this.$lanConnected$.next(false))
|
||||
if (alreadyConnecting) return of()
|
||||
if (enabled && !connected) return markAsLoadingDuring$(this.$testingLanConnection$, this.testLanConnection())
|
||||
if (!enabled && connected) return of(this.$lanConnected$.next(false))
|
||||
return of()
|
||||
}),
|
||||
),
|
||||
// toggle lan
|
||||
combineLatest([this.$lanToggled$, app.lanEnabled, this.$testingLanConnection$]).pipe(
|
||||
filter(([_, __, alreadyLoading]) => !alreadyLoading),
|
||||
map(([e, _]) => [(e as any).detail.checked, _]),
|
||||
distinctUntilChanged(([toggled1], [toggled2]) => toggled1 === toggled2),
|
||||
// if the app is already in the desired state, we bail
|
||||
// this can happen because ionChange triggers when the [checked] value changes
|
||||
filter(([uiEnabled, appEnabled]) => (uiEnabled && !appEnabled) || (!uiEnabled && appEnabled)),
|
||||
concatMap( ([enabled]) => {
|
||||
let o: Observable<void>
|
||||
if (enabled) {
|
||||
o = this.enableLan().pipe(concatMap(() => this.testLanConnection()))
|
||||
} else {
|
||||
o = this.disableLan()
|
||||
}
|
||||
return markAsLoadingDuring$(this.$testingLanConnection$, o).pipe(
|
||||
catchError(e => this.setError(e)),
|
||||
)
|
||||
this.$lanToggled$.pipe(
|
||||
map(toggleEvent => (toggleEvent as any).detail.checked),
|
||||
concatObservableValues([app.lanEnabled, this.$testingLanConnection$]),
|
||||
traceWheel('toggle'),
|
||||
map( ([uiEnabled, appEnabled, alreadyConnecting]) => {
|
||||
if (!alreadyConnecting && uiEnabled && !appEnabled) return this.enableLan().pipe(concatMap(() => this.testLanConnection()))
|
||||
if (!alreadyConnecting && !uiEnabled) return this.disableLan() //do this even if app already disabled because of appModel update timeout hack.
|
||||
return of()
|
||||
}),
|
||||
concatMap((o: Observable<void>) => this.testLanLoader(o)),
|
||||
),
|
||||
),
|
||||
), //must be final in stack
|
||||
@@ -117,6 +109,10 @@ export class AppInstalledShowPage extends Cleanup {
|
||||
)
|
||||
}
|
||||
|
||||
testLanLoader (o: Observable<void>): Observable<void> {
|
||||
return markAsLoadingDuring$(this.$testingLanConnection$, o).pipe(catchError(e => this.setError(e)))
|
||||
}
|
||||
|
||||
testLanConnection () : Observable<void> {
|
||||
if (!this.app.lanAddress) return of()
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@ export class MockApiService extends ApiService {
|
||||
async testConnection (): Promise<true> {
|
||||
console.log('testing connection')
|
||||
this.testCounter ++
|
||||
await pauseFor(10000000)
|
||||
if (this.testCounter > 3) {
|
||||
await pauseFor(500)
|
||||
if (this.testCounter > 2) {
|
||||
return true
|
||||
} else {
|
||||
throw new Error('Not Connected')
|
||||
|
||||
@@ -15,9 +15,6 @@ export class SyncDaemon {
|
||||
private readonly syncInterval = 5000
|
||||
private readonly $sync$ = new BehaviorSubject(false)
|
||||
|
||||
// emits on every successful sync
|
||||
private readonly $synced$ = new Subject<void>()
|
||||
|
||||
constructor (
|
||||
private readonly apiService: ApiService,
|
||||
private readonly serverModel: ServerModel,
|
||||
@@ -39,15 +36,10 @@ export class SyncDaemon {
|
||||
return from(this.getServerAndApps()).pipe(
|
||||
concatMap(() => this.syncNotifier.handleSpecial(this.serverModel.peek())),
|
||||
concatMap(() => this.startupAlertsNotifier.runChecks(this.serverModel.peek())),
|
||||
tap(() => this.$synced$.next()),
|
||||
catchError(e => of(console.error(`Exception in sync service`, e))),
|
||||
)
|
||||
}
|
||||
|
||||
watchSynced (): Observable<void> {
|
||||
return this.$synced$.asObservable()
|
||||
}
|
||||
|
||||
private async getServerAndApps (): Promise<void> {
|
||||
const now = new Date()
|
||||
const [serverRes, appsRes] = await tryAll([
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Observable, from, interval, race, OperatorFunction, Observer, BehaviorSubject } from 'rxjs'
|
||||
import { take, map, switchMap, delay, tap, concatMap } from 'rxjs/operators'
|
||||
import { Observable, from, interval, race, OperatorFunction, Observer, combineLatest } from 'rxjs'
|
||||
import { take, map, concatMap } from 'rxjs/operators'
|
||||
|
||||
export function fromAsync$<S, T> (async: (s: S) => Promise<T>, s: S): Observable<T>
|
||||
export function fromAsync$<T> (async: () => Promise<T>): Observable<T>
|
||||
@@ -17,14 +17,48 @@ export function emitAfter$ (ms: number): Observable<number> {
|
||||
return interval(ms).pipe(take(1))
|
||||
}
|
||||
|
||||
// throws unless source observable emits withing timeout
|
||||
export function throwIn<T> (timeout: number): OperatorFunction<T, T> {
|
||||
return o => race(
|
||||
o,
|
||||
emitAfter$(timeout).pipe(map(() => { throw new Error('timeout') } )))
|
||||
}
|
||||
|
||||
// o.pipe(squash) : Observable<void> regardless of o emission type.
|
||||
export const squash = map(() => { })
|
||||
|
||||
/*
|
||||
The main purpose of fromSync$ is to normalize error handling during a sequence
|
||||
of actions beginning with a standard synchronous action and followed by a pipe.
|
||||
For example, imagine we have `f(s: S): T` which might throw, and we wish to define the following:
|
||||
```
|
||||
function observableF(t: T): Observable<any> {
|
||||
const s = f(t)
|
||||
return someFunctionReturningAnObservable(s)
|
||||
}
|
||||
```
|
||||
|
||||
For the caller, `observableF(t).pipe(...).subscribe({ error: e => console.error('observable errored!') })`
|
||||
might throw an error from `f` which does not result in 'observable errored!' being logged.
|
||||
We could fix this with...
|
||||
```
|
||||
function observableF(t: T): Observable<any> {
|
||||
try {
|
||||
const s = f(t)
|
||||
return someFunctionReturningAnObservable(s)
|
||||
} catch(e) {
|
||||
return throwError(e)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
or we could use fromSync as below
|
||||
```
|
||||
function observableF(t: T): Observable<any> {
|
||||
return fromSync$(f, t).concatMap(someFunctionReturningAnObservable)
|
||||
}
|
||||
```
|
||||
*/
|
||||
export function fromSync$<S, T> (sync: (s: S) => T, s: S): Observable<T>
|
||||
export function fromSync$<T> (sync: () => T): Observable<T>
|
||||
export function fromSync$<S, T> (sync: (s: S) => T, s?: S): Observable<T> {
|
||||
@@ -38,28 +72,23 @@ export function fromSync$<S, T> (sync: (s: S) => T, s?: S): Observable<T> {
|
||||
})
|
||||
}
|
||||
|
||||
export function onCooldown<T> (cooldown: number, o: () => Observable<T>): Observable<T> {
|
||||
|
||||
const $trigger$ = new BehaviorSubject(true)
|
||||
$trigger$.subscribe(t => console.log('triggering', t))
|
||||
return $trigger$.pipe(
|
||||
switchMap(_ =>
|
||||
o().pipe(
|
||||
delay(cooldown),
|
||||
tap(() => $trigger$.next(true)),
|
||||
),
|
||||
/*
|
||||
this function concats the current values (e.g in behavior subjects) or next values (in traditional observables) to a collection of values in a pipe.
|
||||
e.g. if t: Observable<T>, and o1: Observable<O1> o2: Observable<O2> then t.pipe(concatObservableValues([o1, o2])): Observable<[T1, O1, O2]> and emits iff t emits.
|
||||
Note that the standard combineLatest([t, o1, o2]) is also of type Observable<[T, O2, O2]>, but this observable triggers when any of t, o1, o2 emits.
|
||||
*/
|
||||
export function concatObservableValues<T, O> (observables: [Observable<O>]): OperatorFunction<T, [T, O]>
|
||||
export function concatObservableValues<T, O> (observables: [Observable<O>]): OperatorFunction<[T], [T, O]>
|
||||
export function concatObservableValues<T1, T2, O> (observables: [Observable<O>]): OperatorFunction<[T1, T2], [T1, T2, O]>
|
||||
export function concatObservableValues<T, O1, O2> (observables: [Observable<O1>, Observable<O2>]): OperatorFunction<[T], [T, O1, O2]>
|
||||
export function concatObservableValues<T1, T2, O1, O2> (observables: [Observable<O1>, Observable<O2>]): OperatorFunction<[T1, T2], [T1, T2, O1, O2]>
|
||||
export function concatObservableValues (observables: Observable<any>[]): OperatorFunction<any[], any[]> {
|
||||
return o => o.pipe(concatMap(args => combineLatest(observables).pipe(
|
||||
map(obs => {
|
||||
if (!(args instanceof Array)) return [args, ...obs]
|
||||
return [...args, ...obs]
|
||||
}),
|
||||
take(1),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export function bindPipe<T, S1> (o: Observable<T>, then: (t: T) => Observable<S1>): Observable<S1>
|
||||
export function bindPipe<T, S1, S2> (o: Observable<T>, then1: (t: T) => Observable<S1>, then2: (s: S1) => Observable<S2>): Observable<S2>
|
||||
export function bindPipe<T, S1, S2, S3> (o: Observable<T>, then1: (t: T) => Observable<S1>, then2: (s: S1) => Observable<S2>, then3: (s: S2) => Observable<S3>): Observable<S3>
|
||||
export function bindPipe<T, S1, S2, S3, S4> (o: Observable<T>, then1: (t: T) => Observable<S1>, then2: (s: S1) => Observable<S2>, then3: (s: S2) => Observable<S3>, then4: (s: S3) => Observable<S4>): Observable<S4>
|
||||
export function bindPipe<T> (o: Observable<T>, ...thens: ((t: any) => Observable<any>)[]): Observable<any> {
|
||||
const concatted = thens.map(m => concatMap(m))
|
||||
return concatted.reduce( (acc, next) => {
|
||||
return acc.pipe(next)
|
||||
}, o)
|
||||
))
|
||||
}
|
||||
Reference in New Issue
Block a user