diff --git a/projects/components/view/src/view-data-source.ts b/projects/components/view/src/view-data-source.ts index 9d0f7a4..aa6e277 100644 --- a/projects/components/view/src/view-data-source.ts +++ b/projects/components/view/src/view-data-source.ts @@ -1,57 +1,75 @@ -import { computed, signal, Signal } from '@angular/core'; +import { computed, Resource, ResourceStatus, signal, Signal } from '@angular/core'; import { IZvException } from '@zvoove/components/core'; -import { Observable, Subscription, first } from 'rxjs'; +import { first, Observable, of, Subscription } from 'rxjs'; export interface IZvViewDataSource { readonly contentVisible: Signal; readonly contentBlocked: Signal; - readonly exception: Signal; - connect(): void; - disconnect(): void; + readonly error: Signal; + readonly errorIcon: Signal; + connect?(): void; + disconnect?(): void; } -export interface ZvViewDataSourceOptions { - loadTrigger$: Observable; +export interface ZvViewDataSourceOptions { + loadTrigger$?: Observable; loadFn: (params: TParams) => Observable; keepLoadStreamOpen?: boolean; } -export class ZvViewDataSource implements IZvViewDataSource { - private loading = signal(false); +export class ZvViewDataSource implements IZvViewDataSource, Resource { private blockView = signal(false); private connected = false; private params: TParams | null = null; private loadingSub = Subscription.EMPTY; - private connectSub = Subscription.EMPTY; + private loadtriggerSub = Subscription.EMPTY; - constructor(private options: ZvViewDataSourceOptions) {} + constructor(private options: ZvViewDataSourceOptions) {} - public result = signal(null); - public exception = signal(null); + public status = signal(ResourceStatus.Idle); + public value = signal(null!); + public error = signal(null); + public errorIcon = signal('sentiment_very_dissatisfied'); public contentVisible = signal(false); - public contentBlocked = computed(() => this.loading() || this.blockView()); + public contentBlocked = computed(() => this.isLoading() || this.blockView()); + public readonly isLoading = computed(() => this.status() === ResourceStatus.Loading || this.status() === ResourceStatus.Reloading); + public hasValue(): this is Resource> { + return this.value() !== undefined; + } + + /** @deprecated Use value() */ + public result = computed(() => this.value()); + + /** @deprecated Use error() */ + public exception = computed(() => (this.error() ? { errorObject: this.error(), icon: this.errorIcon() } : null)); + + /** @deprecated Use reload() */ + public updateData() { + this.reload(); + } public connect() { if (this.connected) { throw new Error('ViewDataSource is already connected.'); } - this.connectSub = this.options.loadTrigger$.subscribe((params) => { + this.loadtriggerSub = (this.options.loadTrigger$ ?? of(null!)).subscribe((params) => { this.connected = true; this.params = params; this.loadData(params); }); } - public updateData() { + public reload() { if (!this.connected) { throw new Error('ViewDataSource is not connected.'); } this.loadData(this.params!); + return true; } public disconnect(): void { - this.connectSub.unsubscribe(); + this.loadtriggerSub.unsubscribe(); this.loadingSub.unsubscribe(); } @@ -61,9 +79,9 @@ export class ZvViewDataSource implements IZvViewDataSource { private loadData(params: TParams) { this.loadingSub.unsubscribe(); - this.loading.set(true); + this.status.set(ResourceStatus.Loading); this.contentVisible.set(true); - this.exception.set(null); + this.error.set(null); let load$ = this.options.loadFn(params); if (!this.options.keepLoadStreamOpen) { @@ -71,19 +89,48 @@ export class ZvViewDataSource implements IZvViewDataSource { } this.loadingSub = load$.subscribe({ next: (result) => { - this.loading.set(false); - this.result.set(result); + this.status.set(ResourceStatus.Resolved); + this.value.set(result); }, error: (err) => { - this.loading.set(false); - this.result.set(null); + this.status.set(ResourceStatus.Error); + this.value.set(undefined!); this.contentVisible.set(false); - this.exception.set({ - errorObject: err, - alignCenter: true, - icon: 'sentiment_very_dissatisfied', - }); + this.error.set(err); }, }); } } + +export class SignalZvViewDataSource implements IZvViewDataSource, Resource { + public readonly resource: Resource; + public readonly contentVisible = computed(() => this.status() == ResourceStatus.Error); + public readonly contentBlocked = computed(() => this.isLoading() || this.blockView()); + public readonly errorIcon = signal('sentiment_very_dissatisfied'); + + public readonly value: Signal; + public readonly status: Signal; + public readonly error: Signal; + public readonly isLoading: Signal; + public hasValue(): this is Resource> { + return this.resource.hasValue(); + } + + private blockView = signal(false); + + constructor(options: { resource: Resource }) { + this.resource = options.resource; + this.value = this.resource.value.bind(this.resource); + this.status = this.resource.status.bind(this.resource); + this.error = this.resource.error.bind(this.resource); + this.isLoading = this.resource.isLoading.bind(this.resource); + } + + public reload() { + return this.resource.reload(); + } + + public setViewBlocked(value: boolean) { + this.blockView.set(value); + } +} diff --git a/projects/components/view/src/view.component.html b/projects/components/view/src/view.component.html index 48be5f3..486ca20 100644 --- a/projects/components/view/src/view.component.html +++ b/projects/components/view/src/view.component.html @@ -6,12 +6,12 @@ } - @if (dataSource.exception()) { - - @if (dataSource.exception()?.icon) { - {{ dataSource.exception()?.icon }} + @if (dataSource.error()) { + + @if (dataSource.errorIcon()) { + {{ dataSource.errorIcon() }} } - {{ dataSource.exception()?.errorObject | zvErrorMessage }} + {{ dataSource.error() | zvErrorMessage }} } diff --git a/projects/components/view/src/view.component.scss b/projects/components/view/src/view.component.scss index 1e865b5..05782f9 100644 --- a/projects/components/view/src/view.component.scss +++ b/projects/components/view/src/view.component.scss @@ -9,9 +9,6 @@ mat-card.zv-view__error-container { color: var(--zv-components-error); -} - -mat-card.zv-view__error-container--center { display: grid; justify-items: center; } diff --git a/projects/components/view/src/view.component.ts b/projects/components/view/src/view.component.ts index 5eb28c9..687b318 100644 --- a/projects/components/view/src/view.component.ts +++ b/projects/components/view/src/view.component.ts @@ -16,7 +16,7 @@ import { IZvViewDataSource } from './view-data-source'; export class ZvView implements OnDestroy { @Input({ required: true }) public set dataSource(value: IZvViewDataSource) { if (this._dataSource) { - this._dataSource.disconnect(); + this._dataSource.disconnect?.(); } this._dataSource = value; @@ -32,7 +32,7 @@ export class ZvView implements OnDestroy { public ngOnDestroy() { if (this._dataSource) { - this._dataSource.disconnect(); + this._dataSource.disconnect?.(); } } @@ -40,6 +40,6 @@ export class ZvView implements OnDestroy { if (!this._dataSource) { return; } - this._dataSource.connect(); + this._dataSource.connect?.(); } }