Cleaned up Redemptions with use of AsyncPipe & input transformers.
This commit is contained in:
parent
b0f9a2dea8
commit
daa500111c
@ -1,9 +1,8 @@
|
|||||||
import { Component, EventEmitter, inject, input, Input, OnInit, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import RedeemableAction from '../../shared/models/redeemable-action';
|
import RedeemableAction from '../../shared/models/redeemable-action';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -21,20 +20,9 @@ export class ActionDropdownComponent implements OnInit {
|
|||||||
@Input() action: string | undefined;
|
@Input() action: string | undefined;
|
||||||
@Output() readonly actionChange = new EventEmitter<string>();
|
@Output() readonly actionChange = new EventEmitter<string>();
|
||||||
|
|
||||||
private readonly route = inject(ActivatedRoute);
|
|
||||||
|
|
||||||
errorMessageKeys: string[] = []
|
errorMessageKeys: string[] = []
|
||||||
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.route.data.subscribe(data => {
|
|
||||||
if (!data['redeemableActions'])
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.actions = data['redeemableActions'];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.errorMessageKeys = Object.keys(this.errorMessages);
|
this.errorMessageKeys = Object.keys(this.errorMessages);
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ export class ActionsComponent implements OnInit, OnDestroy {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.route.data.subscribe(data => this._actions = data['redeemableActions'] || []);
|
this.route.data.subscribe(data => this._actions = data['redeemableActions'] || []);
|
||||||
|
|
||||||
this.subscriptions.push(this.redeemableActionService.delete$?.subscribe(_ => this.redeemableActionService.fetch().subscribe(a => this._actions = a)));
|
this.subscriptions.push(this.redeemableActionService.changes$?.subscribe(a => this._actions = a));
|
||||||
|
|
||||||
this.client.fetchRedeemableActions();
|
this.client.fetchRedeemableActions();
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, inject, Input, model, OnInit, signal } from '@angular/core';
|
import { Component, inject, OnInit } from '@angular/core';
|
||||||
import Redemption from '../../shared/models/redemption';
|
import Redemption from '../../shared/models/redemption';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
@ -14,8 +14,6 @@ import TwitchRedemption from '../../shared/models/twitch-redemption';
|
|||||||
import RedeemableAction from '../../shared/models/redeemable-action';
|
import RedeemableAction from '../../shared/models/redeemable-action';
|
||||||
import { integerValidator } from '../../shared/validators/integer';
|
import { integerValidator } from '../../shared/validators/integer';
|
||||||
import { createTypeValidator } from '../../shared/validators/of-type';
|
import { createTypeValidator } from '../../shared/validators/of-type';
|
||||||
import RedemptionService from '../../shared/services/redemption.service';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redemption-item-edit',
|
selector: 'redemption-item-edit',
|
||||||
@ -33,10 +31,9 @@ import { Subscription } from 'rxjs';
|
|||||||
styleUrl: './redemption-item-edit.component.scss'
|
styleUrl: './redemption-item-edit.component.scss'
|
||||||
})
|
})
|
||||||
export class RedemptionItemEditComponent implements OnInit {
|
export class RedemptionItemEditComponent implements OnInit {
|
||||||
readonly client = inject(HermesClientService);
|
private readonly client = inject(HermesClientService);
|
||||||
readonly redemptionService = inject(RedemptionService);
|
private readonly dialogRef = inject(MatDialogRef<RedemptionItemEditComponent>);
|
||||||
readonly dialogRef = inject(MatDialogRef<RedemptionItemEditComponent>);
|
private readonly data = inject<{ redemption: Redemption, twitchRedemptions: { [id: string]: TwitchRedemption }, redeemableActions: RedeemableAction[] }>(MAT_DIALOG_DATA);
|
||||||
readonly data = inject<{ redemption: Redemption, twitchRedemptions: TwitchRedemption[], redeemableActions: RedeemableAction[] }>(MAT_DIALOG_DATA);
|
|
||||||
|
|
||||||
redemptionFormControl = new FormControl<TwitchRedemption | string | undefined>(undefined, [Validators.required, createTypeValidator('Object')]);
|
redemptionFormControl = new FormControl<TwitchRedemption | string | undefined>(undefined, [Validators.required, createTypeValidator('Object')]);
|
||||||
redemptionErrorMessages: { [errorKey: string]: string } = {
|
redemptionErrorMessages: { [errorKey: string]: string } = {
|
||||||
@ -66,16 +63,17 @@ export class RedemptionItemEditComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
redemption: Redemption = { id: '', user_id: '', redemption_id: '', action_name: '', order: 0, state: true };
|
redemption: Redemption = { id: '', user_id: '', redemption_id: '', action_name: '', order: 0, state: true };
|
||||||
twitchRedemptions: TwitchRedemption[] = [];
|
twitchRedemptions: { [id: string]: TwitchRedemption } = {};
|
||||||
redeemableActions: RedeemableAction[] = [];
|
redeemableActions: RedeemableAction[] = [];
|
||||||
|
|
||||||
waitForResponse = false;
|
waitForResponse = false;
|
||||||
responseError: string | undefined = undefined;
|
responseError: string | undefined = undefined;
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.redemption = this.data.redemption;
|
this.redemption = this.data.redemption;
|
||||||
this.orderFormControl.setValue(this.redemption.order);
|
|
||||||
this.twitchRedemptions = this.data.twitchRedemptions;
|
this.twitchRedemptions = this.data.twitchRedemptions;
|
||||||
this.redeemableActions = this.data.redeemableActions;
|
this.redeemableActions = this.data.redeemableActions;
|
||||||
|
this.orderFormControl.setValue(this.redemption.order);
|
||||||
this.orderErrorMessageKeys = Object.keys(this.orderErrorMessages);
|
this.orderErrorMessageKeys = Object.keys(this.orderErrorMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +82,9 @@ export class RedemptionItemEditComponent implements OnInit {
|
|||||||
if (this.waitForResponse)
|
if (this.waitForResponse)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.waitForResponse = true
|
this.waitForResponse = true;
|
||||||
|
this.responseError = undefined;
|
||||||
|
|
||||||
const id = this.redemption.id;
|
const id = this.redemption.id;
|
||||||
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'delete_redemption' && d.d.request.data.id == id)
|
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'delete_redemption' && d.d.request.data.id == id)
|
||||||
?.subscribe({
|
?.subscribe({
|
||||||
@ -107,6 +107,7 @@ export class RedemptionItemEditComponent implements OnInit {
|
|||||||
|
|
||||||
this.waitForResponse = true;
|
this.waitForResponse = true;
|
||||||
this.responseError = undefined;
|
this.responseError = undefined;
|
||||||
|
|
||||||
const order = this.orderFormControl.value;
|
const order = this.orderFormControl.value;
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
this.responseError = 'Order must be an integer.';
|
this.responseError = 'Order must be an integer.';
|
||||||
@ -114,16 +115,14 @@ export class RedemptionItemEditComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.waitForResponse = true;
|
|
||||||
const isNew = !this.redemption.id;
|
const isNew = !this.redemption.id;
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'create_redemption' && d.d.request.data.action == (this.redemption.action_name ?? '') && d.d.request.data.redemption == (this.redemption.redemption_id ?? ''))
|
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'create_redemption' && d.d.request.data.action == this.redemption.action_name && d.d.request.data.redemption == this.redemption.redemption_id)
|
||||||
?.subscribe({
|
?.subscribe({
|
||||||
next: (d) => {
|
next: (d) => {
|
||||||
if (d.d.error) {
|
if (d.d.error) {
|
||||||
this.responseError = d.d.error;
|
this.responseError = d.d.error;
|
||||||
} else {
|
} else {
|
||||||
this.redemption.order = order;
|
|
||||||
this.dialogRef.close(d.d.data);
|
this.dialogRef.close(d.d.data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -132,13 +131,13 @@ export class RedemptionItemEditComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
this.client.createRedemption(this.redemption.redemption_id, this.redemption.action_name, order);
|
this.client.createRedemption(this.redemption.redemption_id, this.redemption.action_name, order);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'update_redemption' && d.d.data.id == this.redemption.id)
|
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'update_redemption' && d.d.data.id == this.redemption.id)
|
||||||
?.subscribe({
|
?.subscribe({
|
||||||
next: (d) => {
|
next: (d) => {
|
||||||
if (d.d.error) {
|
if (d.d.error) {
|
||||||
this.responseError = d.d.error;
|
this.responseError = d.d.error;
|
||||||
} else {
|
} else {
|
||||||
this.redemption.order = order;
|
|
||||||
this.dialogRef.close(d.d.data);
|
this.dialogRef.close(d.d.data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -14,10 +14,12 @@
|
|||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
<div class="filters">
|
<div class="filters">
|
||||||
<twitch-redemption-dropdown [(twitchRedemptionId)]="filter_redemption"
|
<twitch-redemption-dropdown [twitchRedemptions]="twitchRedemptions()"
|
||||||
|
[(twitchRedemptionId)]="filter_redemption"
|
||||||
[search]="true" />
|
[search]="true" />
|
||||||
<action-dropdown [(action)]="filter_action_name"
|
<action-dropdown [search]="true"
|
||||||
[search]="true" />
|
[actions]="actions()"
|
||||||
|
[(action)]="filter_action_name" />
|
||||||
</div>
|
</div>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
@ -1,23 +1,19 @@
|
|||||||
import { Component, inject, OnDestroy, signal } from '@angular/core';
|
import { Component, inject, input, signal } from '@angular/core';
|
||||||
import RedemptionService from '../../shared/services/redemption.service';
|
|
||||||
import Redemption from '../../shared/models/redemption';
|
import Redemption from '../../shared/models/redemption';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { TwitchRedemptionDropdownComponent } from "../twitch-redemption-dropdown/twitch-redemption-dropdown.component";
|
import { TwitchRedemptionDropdownComponent } from "../twitch-redemption-dropdown/twitch-redemption-dropdown.component";
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import { ActionDropdownComponent } from '../../actions/action-dropdown/action-dropdown.component';
|
import { ActionDropdownComponent } from '../../actions/action-dropdown/action-dropdown.component';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import TwitchRedemption from '../../shared/models/twitch-redemption';
|
|
||||||
import { RedemptionItemEditComponent } from '../redemption-item-edit/redemption-item-edit.component';
|
import { RedemptionItemEditComponent } from '../redemption-item-edit/redemption-item-edit.component';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import RedeemableAction from '../../shared/models/redeemable-action';
|
import RedeemableAction from '../../shared/models/redeemable-action';
|
||||||
import { MatExpansionModule } from '@angular/material/expansion';
|
import { MatExpansionModule } from '@angular/material/expansion';
|
||||||
import { ScrollingModule } from '@angular/cdk/scrolling';
|
import { ScrollingModule } from '@angular/cdk/scrolling';
|
||||||
import { Subscription } from 'rxjs';
|
import { toTwitchRedemptionDict } from '../../shared/transformers/twitch-redemption.transformer';
|
||||||
import { HermesClientService } from '../../hermes-client.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redemption-list',
|
selector: 'redemption-list',
|
||||||
@ -36,97 +32,45 @@ import { HermesClientService } from '../../hermes-client.service';
|
|||||||
templateUrl: './redemption-list.component.html',
|
templateUrl: './redemption-list.component.html',
|
||||||
styleUrl: './redemption-list.component.scss'
|
styleUrl: './redemption-list.component.scss'
|
||||||
})
|
})
|
||||||
export class RedemptionListComponent implements OnDestroy {
|
export class RedemptionListComponent {
|
||||||
private readonly client = inject(HermesClientService);
|
|
||||||
private readonly redemptionService = inject(RedemptionService);
|
|
||||||
private readonly route = inject(ActivatedRoute);
|
|
||||||
readonly dialog = inject(MatDialog);
|
readonly dialog = inject(MatDialog);
|
||||||
private _redemptions: Redemption[] = [];
|
|
||||||
private _twitchRedemptions: TwitchRedemption[] = [];
|
|
||||||
private _twitchRedemptionsDict: { [id: string]: string } = {};
|
|
||||||
private _actions: RedeemableAction[] = [];
|
|
||||||
displayedColumns: string[] = ['twitch-redemption', 'action-name', 'order', 'misc'];
|
displayedColumns: string[] = ['twitch-redemption', 'action-name', 'order', 'misc'];
|
||||||
filter_redemption: string | undefined;
|
filter_redemption: string | undefined;
|
||||||
filter_action_name: string | undefined;
|
filter_action_name: string | undefined;
|
||||||
readonly panelOpenState = signal(false);
|
readonly panelOpenState = signal(false);
|
||||||
private _subscriptions: Subscription[] = []
|
|
||||||
|
|
||||||
|
_redemptions = input.required<Redemption[]>({ alias: 'redemptions' });
|
||||||
|
twitchRedemptions = input.required({
|
||||||
|
alias: 'twitchRedemptions',
|
||||||
|
transform: toTwitchRedemptionDict,
|
||||||
|
});
|
||||||
|
//twitchRedemptions = computed<{ [id: string]: string } | null>(() => ({}));
|
||||||
|
actions = input.required<RedeemableAction[]>();
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.route.data.subscribe(r => {
|
|
||||||
this._twitchRedemptions = r['twitchRedemptions'];
|
|
||||||
this._twitchRedemptionsDict = Object.assign({}, ...r['twitchRedemptions'].map((t: TwitchRedemption) => ({ [t.id]: t.title })));
|
|
||||||
this._actions = r['redeemableActions'];
|
|
||||||
|
|
||||||
let redemptions = [...r['redemptions']];
|
|
||||||
redemptions.sort((a: Redemption, b: Redemption) => this.compare(a, b));
|
|
||||||
this._redemptions = redemptions;
|
|
||||||
});
|
|
||||||
|
|
||||||
let subscription = this.redemptionService.create$?.subscribe(d => {
|
|
||||||
if (d.error || !d.data || d.request.nounce != null && d.request.nounce.startsWith(this.client.session_id))
|
|
||||||
return;
|
|
||||||
|
|
||||||
let index = -1;
|
|
||||||
for (let i = 0; i < this._redemptions.length; i++) {
|
|
||||||
const comp = this.compare(d.data, this._redemptions[i]);
|
|
||||||
if (comp < 0) {
|
|
||||||
index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._redemptions.splice(index >= 0 ? index : this._redemptions.length, 0, d.data);
|
|
||||||
});
|
|
||||||
if (subscription)
|
|
||||||
this._subscriptions.push(subscription);
|
|
||||||
|
|
||||||
subscription = this.redemptionService.update$?.subscribe(d => {
|
|
||||||
if (d.error || !d.data || d.request.nounce != null && d.request.nounce.startsWith(this.client.session_id))
|
|
||||||
return;
|
|
||||||
|
|
||||||
const redemption = this._redemptions.find(r => r.id = d.data.id);
|
|
||||||
if (redemption) {
|
|
||||||
redemption.action_name = d.data.action_name;
|
|
||||||
redemption.redemption_id = d.data.redemption_id;
|
|
||||||
redemption.order = d.data.order;
|
|
||||||
redemption.state = d.data.state;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (subscription)
|
|
||||||
this._subscriptions.push(subscription);
|
|
||||||
|
|
||||||
subscription = this.redemptionService.delete$?.subscribe(d => {
|
|
||||||
if (d.error || d.request.nounce != null && d.request.nounce.startsWith(this.client.session_id))
|
|
||||||
return;
|
|
||||||
|
|
||||||
this._redemptions = this._redemptions.filter(r => r.id != d.request.data.id);
|
|
||||||
});
|
|
||||||
if (subscription)
|
|
||||||
this._subscriptions.push(subscription);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.panelOpenState.set(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this._subscriptions.forEach(s => s.unsubscribe());
|
|
||||||
}
|
|
||||||
|
|
||||||
compare(a: Redemption, b: Redemption) {
|
compare(a: Redemption, b: Redemption) {
|
||||||
return this._twitchRedemptionsDict[a.redemption_id].localeCompare(this._twitchRedemptionsDict[b.redemption_id]) || a.order - b.order;
|
const dict = this.twitchRedemptions();
|
||||||
|
if (!dict) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return dict[a.redemption_id]?.title.localeCompare(dict[b.redemption_id]?.title) || a.order - b.order;
|
||||||
}
|
}
|
||||||
|
|
||||||
get redemptions() {
|
get redemptions() {
|
||||||
const redemptionFilter = this.filter_redemption?.toString().toLowerCase();
|
|
||||||
const actionFilter = this.filter_action_name?.toString().toLowerCase();
|
const actionFilter = this.filter_action_name?.toString().toLowerCase();
|
||||||
let filtered = this._redemptions.filter(r => !redemptionFilter || this._twitchRedemptionsDict[r.redemption_id].toLowerCase().includes(redemptionFilter));
|
const redemptionFilter = this.filter_redemption?.toString().toLowerCase();
|
||||||
filtered = filtered.filter(r => !actionFilter || r.action_name.toLowerCase().includes(actionFilter));
|
let filtered = this._redemptions();
|
||||||
|
if (redemptionFilter) {
|
||||||
|
filtered = filtered.filter(r => this.twitchRedemptions()![r.redemption_id]?.title.toLowerCase().includes(redemptionFilter) || r.redemption_id == redemptionFilter);
|
||||||
|
}
|
||||||
|
if (actionFilter) {
|
||||||
|
filtered = filtered.filter(r => !actionFilter || r.action_name.toLowerCase().includes(actionFilter));
|
||||||
|
}
|
||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTwitchRedemptionNameById(id: string) {
|
getTwitchRedemptionNameById(id: string) {
|
||||||
return this._twitchRedemptionsDict[id];
|
return this.twitchRedemptions()![id]?.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(): void {
|
add(): void {
|
||||||
@ -134,39 +78,9 @@ export class RedemptionListComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openDialog(redemption: Redemption): void {
|
openDialog(redemption: Redemption): void {
|
||||||
const dialogRef = this.dialog.open(RedemptionItemEditComponent, {
|
this.dialog.open(RedemptionItemEditComponent, {
|
||||||
data: { redemption: { ...redemption }, twitchRedemptions: this._twitchRedemptions, redeemableActions: this._actions },
|
data: { redemption: { ...redemption }, twitchRedemptions: this.twitchRedemptions(), redeemableActions: this.actions() },
|
||||||
maxWidth: '100vw'
|
maxWidth: '100vw'
|
||||||
});
|
});
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe((result: Redemption | string) => {
|
|
||||||
if (!result)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (typeof result === 'string') {
|
|
||||||
// Deleted
|
|
||||||
this._redemptions = this._redemptions.filter(r => r.id != result);
|
|
||||||
} else {
|
|
||||||
const redemption = this._redemptions.find(r => r.id == result.id);
|
|
||||||
if (redemption) {
|
|
||||||
// Updated
|
|
||||||
redemption.action_name = result.action_name;
|
|
||||||
redemption.redemption_id = result.redemption_id;
|
|
||||||
redemption.order = result.order;
|
|
||||||
redemption.state = result.state;
|
|
||||||
} else {
|
|
||||||
// Created
|
|
||||||
let index = -1;
|
|
||||||
for (let i = 0; i < this._redemptions.length; i++) {
|
|
||||||
const comp = this.compare(result, this._redemptions[i]);
|
|
||||||
if (comp < 0) {
|
|
||||||
index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._redemptions.splice(index >= 0 ? index : this._redemptions.length, 0, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,7 @@
|
|||||||
<div class="root">
|
<div class="root">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<redemption-list />
|
<redemption-list [redemptions]="redemptions$ | async"
|
||||||
|
[twitchRedemptions]="twitchRedemptions$ | async"
|
||||||
|
[actions]="actions$ | async" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -1,26 +1,25 @@
|
|||||||
import { Component, inject, OnInit } from '@angular/core';
|
import { Component, inject } from '@angular/core';
|
||||||
import { RedemptionListComponent } from "../redemption-list/redemption-list.component";
|
import { RedemptionListComponent } from "../redemption-list/redemption-list.component";
|
||||||
import { HermesClientService } from '../../hermes-client.service';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import RedemptionService from '../../shared/services/redemption.service';
|
import RedemptionService from '../../shared/services/redemption.service';
|
||||||
import { Observable, of } from 'rxjs';
|
import { AsyncPipe } from '@angular/common';
|
||||||
import Redemption from '../../shared/models/redemption';
|
import TwitchRedemptionService from '../../shared/services/twitch-redemption.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import RedeemableActionService from '../../shared/services/redeemable-action.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redemptions',
|
selector: 'redemptions',
|
||||||
imports: [RedemptionListComponent],
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
RedemptionListComponent
|
||||||
|
],
|
||||||
templateUrl: './redemptions.component.html',
|
templateUrl: './redemptions.component.html',
|
||||||
styleUrl: './redemptions.component.scss'
|
styleUrl: './redemptions.component.scss'
|
||||||
})
|
})
|
||||||
export class RedemptionsComponent implements OnInit {
|
export class RedemptionsComponent {
|
||||||
client = inject(HermesClientService);
|
private readonly twitchRedemptionService = inject(TwitchRedemptionService);
|
||||||
http = inject(HttpClient);
|
private readonly redemptionService = inject(RedemptionService);
|
||||||
route = inject(ActivatedRoute);
|
private readonly actionService = inject(RedeemableActionService);
|
||||||
redemptionService = inject(RedemptionService);
|
|
||||||
redemptions: Observable<Redemption[]> | undefined;
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
redemptions$ = this.redemptionService.changes$;
|
||||||
|
twitchRedemptions$ = this.twitchRedemptionService.fetch();
|
||||||
}
|
actions$ = this.actionService.changes$;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<mat-autocomplete #auto="matAutocomplete"
|
<mat-autocomplete #auto="matAutocomplete"
|
||||||
[displayWith]="displayFn"
|
[displayWith]="displayFn"
|
||||||
(optionSelected)="select($event.option.value)">
|
(optionSelected)="select($event.option.value)">
|
||||||
@for (redemption of filteredRedemptions; track redemption.id) {
|
@for (redemption of filteredRedemptions; track redemption.title) {
|
||||||
<mat-option [value]="redemption">{{redemption.title}}</mat-option>
|
<mat-option [value]="redemption">{{redemption.title}}</mat-option>
|
||||||
}
|
}
|
||||||
</mat-autocomplete>
|
</mat-autocomplete>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Component, EventEmitter, inject, input, Input, OnInit, Output } from '@angular/core';
|
import { Component, input, Input, model, OnInit } from '@angular/core';
|
||||||
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import TwitchRedemption from '../../shared/models/twitch-redemption';
|
import TwitchRedemption from '../../shared/models/twitch-redemption';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { toTwitchRedemptionArray } from '../../shared/transformers/twitch-redemption.transformer';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'twitch-redemption-dropdown',
|
selector: 'twitch-redemption-dropdown',
|
||||||
@ -16,69 +16,60 @@ export class TwitchRedemptionDropdownComponent implements OnInit {
|
|||||||
@Input() formControl = new FormControl<TwitchRedemption | string | undefined>(undefined);
|
@Input() formControl = new FormControl<TwitchRedemption | string | undefined>(undefined);
|
||||||
@Input() errorMessages: { [errorKey: string]: string } = {};
|
@Input() errorMessages: { [errorKey: string]: string } = {};
|
||||||
|
|
||||||
@Input() search: boolean = false;
|
twitchRedemptions = input.required({
|
||||||
@Input() twitchRedemptions: TwitchRedemption[] = [];
|
transform: toTwitchRedemptionArray
|
||||||
@Input() twitchRedemptionId: string | undefined;
|
});
|
||||||
@Output() readonly twitchRedemptionIdChange = new EventEmitter<string>();
|
twitchRedemptionId = model<string | undefined>();
|
||||||
|
search = input<boolean>(false);
|
||||||
private readonly route = inject(ActivatedRoute);
|
|
||||||
|
|
||||||
errorMessageKeys: string[] = [];
|
errorMessageKeys: string[] = [];
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.route.data.subscribe(data => {
|
|
||||||
if (!data['twitchRedemptions'])
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.twitchRedemptions = data['twitchRedemptions'];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (this.twitchRedemptions() && this.twitchRedemptionId()) {
|
||||||
|
const redemption = this.twitchRedemptions()!.find(r => r.id == this.twitchRedemptionId());
|
||||||
|
this.formControl.setValue(redemption);
|
||||||
|
}
|
||||||
this.errorMessageKeys = Object.keys(this.errorMessages);
|
this.errorMessageKeys = Object.keys(this.errorMessages);
|
||||||
|
|
||||||
if (!this.twitchRedemptionId || !this.twitchRedemptions)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const redemption = this.twitchRedemptions.find(r => r.id == this.twitchRedemptionId);
|
|
||||||
this.formControl.setValue(redemption);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get filteredRedemptions() {
|
get filteredRedemptions() {
|
||||||
const value = this.formControl.value;
|
const value = this.formControl.value;
|
||||||
if (typeof (value) == 'string') {
|
if (this.twitchRedemptions() && typeof (value) == 'string') {
|
||||||
return this.twitchRedemptions.filter(r => r.title.toLowerCase().includes(value.toLowerCase()));
|
return this.twitchRedemptions()!.filter(r => r.title.toLowerCase().includes(value.toLowerCase()));
|
||||||
}
|
}
|
||||||
return this.twitchRedemptions;
|
return this.twitchRedemptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
select(event: TwitchRedemption) {
|
select(event: TwitchRedemption) {
|
||||||
this.twitchRedemptionIdChange.emit(event.id);
|
this.twitchRedemptionId.set(event.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
input() {
|
input() {
|
||||||
if (this.search && typeof this.formControl.value == 'string') {
|
if (this.search() && typeof this.formControl.value == 'string') {
|
||||||
this.twitchRedemptionIdChange.emit(this.formControl.value);
|
this.twitchRedemptionId.set(this.formControl.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blur() {
|
blur() {
|
||||||
|
if (this.filteredRedemptions == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!this.search && typeof this.formControl.value == 'string') {
|
if (!this.search && typeof this.formControl.value == 'string') {
|
||||||
const name = this.formControl.value;
|
const name = this.formControl.value;
|
||||||
const nameLower = name.toLowerCase();
|
const nameLower = name.toLowerCase();
|
||||||
let newValue: TwitchRedemption | undefined = undefined;
|
let newValue: TwitchRedemption | undefined;
|
||||||
const insenstiveActions = this.filteredRedemptions.filter(a => a.title.toLowerCase() == nameLower);
|
const filtered = this.filteredRedemptions?.filter(a => a.title.toLowerCase() == nameLower);
|
||||||
if (insenstiveActions.length > 1) {
|
if (filtered.length > 1) {
|
||||||
const sensitiveAction = insenstiveActions.find(a => a.title == name);
|
newValue = filtered.find(a => a.title == name);
|
||||||
newValue = sensitiveAction ?? undefined;
|
} else if (filtered.length == 1) {
|
||||||
} else if (insenstiveActions.length == 1) {
|
newValue = filtered[0];
|
||||||
newValue = insenstiveActions[0];
|
|
||||||
}
|
}
|
||||||
if (newValue && newValue.id != this.formControl.value) {
|
if (newValue && newValue.id != this.formControl.value) {
|
||||||
this.formControl.setValue(newValue);
|
this.formControl.setValue(newValue);
|
||||||
this.twitchRedemptionIdChange.emit(newValue.id);
|
this.twitchRedemptionId.set(newValue.id);
|
||||||
} else if (!newValue)
|
} else if (!newValue)
|
||||||
this.twitchRedemptionIdChange.emit(undefined);
|
this.twitchRedemptionId.set(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { HermesClientService } from '../../hermes-client.service';
|
import { HermesClientService } from '../../hermes-client.service';
|
||||||
import { map, Observable, of } from 'rxjs';
|
import { map, merge, Observable, of, startWith } from 'rxjs';
|
||||||
import RedeemableAction from '../models/redeemable-action';
|
import RedeemableAction from '../models/redeemable-action';
|
||||||
import EventService from './EventService';
|
import EventService from './EventService';
|
||||||
|
|
||||||
@ -12,6 +12,7 @@ export default class RedeemableActionService {
|
|||||||
private readonly events = inject(EventService);
|
private readonly events = inject(EventService);
|
||||||
private data: RedeemableAction[] = [];
|
private data: RedeemableAction[] = [];
|
||||||
private loaded = false;
|
private loaded = false;
|
||||||
|
changes$: Observable<any>;
|
||||||
create$: Observable<any> | undefined;
|
create$: Observable<any> | undefined;
|
||||||
update$: Observable<any> | undefined;
|
update$: Observable<any> | undefined;
|
||||||
delete$: Observable<any> | undefined;
|
delete$: Observable<any> | undefined;
|
||||||
@ -21,6 +22,11 @@ export default class RedeemableActionService {
|
|||||||
this.create$ = this.client.filterByRequestType('create_redeemable_action');
|
this.create$ = this.client.filterByRequestType('create_redeemable_action');
|
||||||
this.update$ = this.client.filterByRequestType('update_redeemable_action');
|
this.update$ = this.client.filterByRequestType('update_redeemable_action');
|
||||||
this.delete$ = this.client.filterByRequestType('delete_redeemable_action');
|
this.delete$ = this.client.filterByRequestType('delete_redeemable_action');
|
||||||
|
this.changes$ = merge<any>(this.create$, this.update$, this.delete$)
|
||||||
|
.pipe(
|
||||||
|
startWith(null),
|
||||||
|
map(d => this.data.slice()),
|
||||||
|
);
|
||||||
|
|
||||||
this.create$?.subscribe(d => this.data.push(d.data));
|
this.create$?.subscribe(d => this.data.push(d.data));
|
||||||
this.update$?.subscribe(d => {
|
this.update$?.subscribe(d => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import Redemption from '../models/redemption';
|
import Redemption from '../models/redemption';
|
||||||
import { HermesClientService } from '../../hermes-client.service';
|
import { HermesClientService } from '../../hermes-client.service';
|
||||||
import { map, Observable, of } from 'rxjs';
|
import { map, merge, Observable, of, startWith } from 'rxjs';
|
||||||
import EventService from './EventService';
|
import EventService from './EventService';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@ -12,6 +12,7 @@ export default class RedemptionService {
|
|||||||
private readonly events = inject(EventService);
|
private readonly events = inject(EventService);
|
||||||
private data: Redemption[] = []
|
private data: Redemption[] = []
|
||||||
private loaded = false;
|
private loaded = false;
|
||||||
|
changes$: Observable<any>;
|
||||||
create$: Observable<any> | undefined;
|
create$: Observable<any> | undefined;
|
||||||
update$: Observable<any> | undefined;
|
update$: Observable<any> | undefined;
|
||||||
delete$: Observable<any> | undefined;
|
delete$: Observable<any> | undefined;
|
||||||
@ -20,6 +21,11 @@ export default class RedemptionService {
|
|||||||
this.create$ = this.client.filterByRequestType('create_redemption');
|
this.create$ = this.client.filterByRequestType('create_redemption');
|
||||||
this.update$ = this.client.filterByRequestType('update_redemption');
|
this.update$ = this.client.filterByRequestType('update_redemption');
|
||||||
this.delete$ = this.client.filterByRequestType('delete_redemption');
|
this.delete$ = this.client.filterByRequestType('delete_redemption');
|
||||||
|
this.changes$ = merge<any>(this.create$, this.update$, this.delete$)
|
||||||
|
.pipe(
|
||||||
|
startWith(null),
|
||||||
|
map(d => this.data.slice()),
|
||||||
|
);
|
||||||
|
|
||||||
this.create$?.subscribe(d => this.data.push(d.data));
|
this.create$?.subscribe(d => this.data.push(d.data));
|
||||||
this.update$?.subscribe(d => {
|
this.update$?.subscribe(d => {
|
||||||
|
@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http';
|
|||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import TwitchRedemption from '../models/twitch-redemption';
|
import TwitchRedemption from '../models/twitch-redemption';
|
||||||
import { catchError, EMPTY, Observable, of } from 'rxjs';
|
import { catchError, EMPTY, of } from 'rxjs';
|
||||||
import EventService from './EventService';
|
import EventService from './EventService';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@ -11,6 +11,7 @@ import EventService from './EventService';
|
|||||||
export default class TwitchRedemptionService {
|
export default class TwitchRedemptionService {
|
||||||
private readonly http = inject(HttpClient);
|
private readonly http = inject(HttpClient);
|
||||||
private readonly events = inject(EventService);
|
private readonly events = inject(EventService);
|
||||||
|
|
||||||
private twitchRedemptions: TwitchRedemption[] = [];
|
private twitchRedemptions: TwitchRedemption[] = [];
|
||||||
private loaded = false;
|
private loaded = false;
|
||||||
|
|
||||||
|
18
src/app/shared/transformers/twitch-redemption.transformer.ts
Normal file
18
src/app/shared/transformers/twitch-redemption.transformer.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import TwitchRedemption from "../models/twitch-redemption";
|
||||||
|
import { toDictStringKeyed } from "../utils/array.transformer";
|
||||||
|
|
||||||
|
export function toTwitchRedemptionDict(values: TwitchRedemption[] | null): { [key: string]: TwitchRedemption } {
|
||||||
|
if (!values) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return toDictStringKeyed(values, r => r.id, r => r);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toTwitchRedemptionArray(values: { [id: string]: TwitchRedemption } | null): TwitchRedemption[] {
|
||||||
|
if (!values) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.values(values);
|
||||||
|
}
|
7
src/app/shared/utils/array.transformer.ts
Normal file
7
src/app/shared/utils/array.transformer.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export function toDictStringKeyed<T, V>(values: T[], keyGetter: { (k: T): string }, valueGetter: { (v: T): V }): { [key: string]: V } {
|
||||||
|
return Object.assign({}, ...values.map((t: T) => ({ [keyGetter(t)]: valueGetter(t) })))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toDictNumberKeyed<T, V>(values: T[], keyGetter: { (k: T): number }, valueGetter: { (v: T): V }): { [key: number]: V } {
|
||||||
|
return Object.assign({}, ...values.map((t: T) => ({ [keyGetter(t)]: valueGetter(t) })))
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user