Added user management for groups. Improved user experience slightly. Added some error checks for request acks.
This commit is contained in:
parent
2f2215b041
commit
1acda7978e
@ -108,7 +108,8 @@
|
|||||||
}
|
}
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
|
|
||||||
<mat-card-actions class="actions">
|
<mat-card-actions class="actions"
|
||||||
|
align="end">
|
||||||
@if (!isNew) {
|
@if (!isNew) {
|
||||||
<button mat-raised-button
|
<button mat-raised-button
|
||||||
class="delete"
|
class="delete"
|
||||||
|
@ -213,9 +213,9 @@ export class ActionItemEditComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.isNew = this.action.name.length <= 0;
|
this.isNew = this.action.name.length <= 0;
|
||||||
this.previousName = this.action.name;
|
this.previousName = this.action.name;
|
||||||
if (!this.isNew)
|
if (!this.isNew) {
|
||||||
this.formGroup.get('name')!.disable()
|
this.formGroup.get('name')!.disable()
|
||||||
else {
|
} else {
|
||||||
this.formGroup.get('name')?.addValidators(createItemExistsInArrayValidator(this.actions, a => a.name));
|
this.formGroup.get('name')?.addValidators(createItemExistsInArrayValidator(this.actions, a => a.name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,7 +241,13 @@ export class ActionItemEditComponent implements OnInit {
|
|||||||
this.waitForResponse = true;
|
this.waitForResponse = true;
|
||||||
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'delete_redeemable_action' && d.d.request.data.name == this.action.name)
|
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'delete_redeemable_action' && d.d.request.data.name == this.action.name)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: () => this.dialogRef.close(),
|
next: (d) => {
|
||||||
|
if (d.d.error) {
|
||||||
|
// TODO: update & show response error message.
|
||||||
|
} else {
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
error: () => this.waitForResponse = false,
|
error: () => this.waitForResponse = false,
|
||||||
complete: () => this.waitForResponse = false,
|
complete: () => this.waitForResponse = false,
|
||||||
});
|
});
|
||||||
@ -277,7 +283,13 @@ export class ActionItemEditComponent implements OnInit {
|
|||||||
const requestType = isNewAction ? 'create_redeemable_action' : 'update_redeemable_action';
|
const requestType = isNewAction ? 'create_redeemable_action' : 'update_redeemable_action';
|
||||||
this.client.first((d: any) => d.op == 4 && d.d.request.type == requestType && d.d.data.name == this.action.name)
|
this.client.first((d: any) => d.op == 4 && d.d.request.type == requestType && d.d.data.name == this.action.name)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: () => this.dialogRef.close(this.action),
|
next: (d) => {
|
||||||
|
if (d.d.error) {
|
||||||
|
// TODO: update & show response error message.
|
||||||
|
} else {
|
||||||
|
this.dialogRef.close(this.action);
|
||||||
|
}
|
||||||
|
},
|
||||||
error: () => this.waitForResponse = false,
|
error: () => this.waitForResponse = false,
|
||||||
complete: () => this.waitForResponse = false,
|
complete: () => this.waitForResponse = false,
|
||||||
});
|
});
|
||||||
|
@ -5,8 +5,7 @@ import { MatSelectModule } from '@angular/material/select';
|
|||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import EventService from '../../shared/services/EventService';
|
import EventService from '../../shared/services/EventService';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { first, Subscription, timeout } from 'rxjs';
|
import { first, Subscription, timeout } from 'rxjs';
|
||||||
import { HermesClientService } from '../../hermes-client.service';
|
import { HermesClientService } from '../../hermes-client.service';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
@ -20,16 +19,16 @@ import { ApiKeyService } from '../../shared/services/api/api-key.service';
|
|||||||
styleUrl: './tts-login.component.scss'
|
styleUrl: './tts-login.component.scss'
|
||||||
})
|
})
|
||||||
export class TtsLoginComponent implements OnInit, OnDestroy {
|
export class TtsLoginComponent implements OnInit, OnDestroy {
|
||||||
keyService = inject(ApiKeyService);
|
private readonly client = inject(HermesClientService);
|
||||||
route = inject(ActivatedRoute);
|
private readonly keyService = inject(ApiKeyService);
|
||||||
|
private readonly events = inject(EventService);
|
||||||
|
private readonly route = inject(ActivatedRoute);
|
||||||
|
|
||||||
api_keys: { id: string, label: string }[] = [];
|
api_keys: { id: string, label: string }[] = [];
|
||||||
selected_api_key: string | undefined;
|
selected_api_key: string | undefined;
|
||||||
|
|
||||||
private subscriptions: Subscription[] = [];
|
private subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
constructor(private hermes: HermesClientService, private events: EventService, private http: HttpClient, private router: Router) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.route.data.subscribe(d => this.api_keys = d['keys']);
|
this.route.data.subscribe(d => this.api_keys = d['keys']);
|
||||||
@ -50,10 +49,10 @@ export class TtsLoginComponent implements OnInit, OnDestroy {
|
|||||||
this.subscriptions.forEach(s => s.unsubscribe());
|
this.subscriptions.forEach(s => s.unsubscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
login() {
|
login(): void {
|
||||||
if (!this.selected_api_key)
|
if (!this.selected_api_key)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.hermes.login(this.selected_api_key);
|
this.client.login(this.selected_api_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,8 @@
|
|||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<impersonation />
|
<impersonation />
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions class="actions">
|
<mat-card-actions class="actions"
|
||||||
|
align="end">
|
||||||
<div>
|
<div>
|
||||||
@if (isTTSLoggedIn) {
|
@if (isTTSLoggedIn) {
|
||||||
<button mat-raised-button
|
<button mat-raised-button
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
}
|
}
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions>
|
<mat-card-actions align="end">
|
||||||
<button mat-button
|
<button mat-button
|
||||||
[disabled]="waitForResponse || formGroup.invalid"
|
[disabled]="waitForResponse || formGroup.invalid"
|
||||||
(click)="add()">
|
(click)="add()">
|
||||||
|
@ -55,7 +55,13 @@ export class GroupItemEditComponent implements OnInit {
|
|||||||
this.waitForResponse = true;
|
this.waitForResponse = true;
|
||||||
this._client.first((d: any) => d.op == 4 && d.d.request.type == 'create_group' && d.d.data.name == this.nameForm.value)
|
this._client.first((d: any) => d.op == 4 && d.d.request.type == 'create_group' && d.d.data.name == this.nameForm.value)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (d) => this._dialogRef.close(d.d.data),
|
next: (d) => {
|
||||||
|
if (d.d.error) {
|
||||||
|
// TODO: update & show response error message.
|
||||||
|
} else {
|
||||||
|
this._dialogRef.close(d.d.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
error: () => this.waitForResponse = false,
|
error: () => this.waitForResponse = false,
|
||||||
complete: () => this.waitForResponse = false,
|
complete: () => this.waitForResponse = false,
|
||||||
});
|
});
|
||||||
|
@ -4,10 +4,17 @@
|
|||||||
<small class="tag">auto-generated</small>
|
<small class="tag">auto-generated</small>
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
<section class="">{{item().group.priority}}</section>
|
<section class="">
|
||||||
|
{{item().group.priority}}
|
||||||
|
<small class="muted block">priority</small>
|
||||||
|
</section>
|
||||||
<section>
|
<section>
|
||||||
|
@if (special) {
|
||||||
|
<p class="muted">Unknown</p>
|
||||||
|
} @else {
|
||||||
{{item().chatters.length}}
|
{{item().chatters.length}}
|
||||||
<small class="muted block">user{{item().chatters.length == 1 ? '' : 's'}}</small>
|
<small class="muted block">user{{item().chatters.length == 1 ? '' : 's'}}</small>
|
||||||
|
}
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
{{item().policies.length}}
|
{{item().policies.length}}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import { Component, inject, input, Input, OnInit } from '@angular/core';
|
import { Component, inject, input, OnInit } from '@angular/core';
|
||||||
import { Group } from '../../shared/models/group';
|
import { Group } from '../../shared/models/group';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
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 { Policy } from '../../shared/models/policy';
|
import { Policy } from '../../shared/models/policy';
|
||||||
import { GroupItemEditComponent } from '../group-item-edit/group-item-edit.component';
|
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { GroupChatter } from '../../shared/models/group-chatter';
|
import { GroupChatter } from '../../shared/models/group-chatter';
|
||||||
|
import { SpecialGroups } from '../../shared/utils/groups';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'group-item',
|
selector: 'group-item',
|
||||||
@ -24,12 +23,10 @@ export class GroupItemComponent implements OnInit {
|
|||||||
readonly router = inject(Router);
|
readonly router = inject(Router);
|
||||||
item = input.required<{ group: Group, chatters: GroupChatter[], policies: Policy[] }>();
|
item = input.required<{ group: Group, chatters: GroupChatter[], policies: Policy[] }>();
|
||||||
link: string = '';
|
link: string = '';
|
||||||
|
|
||||||
|
|
||||||
special: boolean = true;
|
special: boolean = true;
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.special = ['everyone', 'subscribers', 'moderators', 'vip', 'broadcaster'].includes(this.item().group.name);
|
this.special = SpecialGroups.includes(this.item().group.name);
|
||||||
this.link = 'groups/' + this.item().group.id;
|
this.link = 'groups/' + this.item().group.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2>{{group?.name}}</h2>
|
<h2>{{group?.name}}</h2>
|
||||||
|
|
||||||
|
@if (!isSpecialGroup) {
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>Users</mat-panel-title>
|
||||||
|
<mat-panel-description class="muted">
|
||||||
|
{{chatters.length}} user{{chatters.length == 1 ? '' : 's'}}
|
||||||
|
</mat-panel-description>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<twitch-user-list [twitchUsers]="chatters"
|
||||||
|
[group]="group" />
|
||||||
|
</mat-expansion-panel>
|
||||||
|
}
|
||||||
|
|
||||||
<mat-expansion-panel>
|
<mat-expansion-panel>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-panel-title>Policies</mat-panel-title>
|
<mat-panel-title>Policies</mat-panel-title>
|
||||||
@ -12,9 +25,7 @@
|
|||||||
[groups]="groups"
|
[groups]="groups"
|
||||||
[policies]="policies"
|
[policies]="policies"
|
||||||
[group]="group?.id" />
|
[group]="group?.id" />
|
||||||
@if (policies.length > 0) {
|
|
||||||
<policy-table [policies]="policies" />
|
<policy-table [policies]="policies" />
|
||||||
}
|
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
|
||||||
<mat-expansion-panel>
|
<mat-expansion-panel>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, inject } from '@angular/core';
|
import { Component, inject, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { Group } from '../../shared/models/group';
|
import { Group } from '../../shared/models/group';
|
||||||
import { Policy } from '../../shared/models/policy';
|
import { Policy } from '../../shared/models/policy';
|
||||||
@ -13,9 +13,10 @@ import { PolicyTableComponent } from "../../policies/policy-table/policy-table.c
|
|||||||
import { PolicyAddButtonComponent } from '../../policies/policy-add-button/policy-add-button.component';
|
import { PolicyAddButtonComponent } from '../../policies/policy-add-button/policy-add-button.component';
|
||||||
import { HermesClientService } from '../../hermes-client.service';
|
import { HermesClientService } from '../../hermes-client.service';
|
||||||
import { GroupChatter } from '../../shared/models/group-chatter';
|
import { GroupChatter } from '../../shared/models/group-chatter';
|
||||||
|
import { TwitchUsersModule } from "../../twitch-users/twitch-users.module";
|
||||||
|
import { SpecialGroups } from '../../shared/utils/groups';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'group-page',
|
|
||||||
imports: [
|
imports: [
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatExpansionModule,
|
MatExpansionModule,
|
||||||
@ -25,7 +26,9 @@ import { GroupChatter } from '../../shared/models/group-chatter';
|
|||||||
PoliciesModule,
|
PoliciesModule,
|
||||||
PolicyAddButtonComponent,
|
PolicyAddButtonComponent,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
PolicyTableComponent
|
PolicyTableComponent,
|
||||||
|
PolicyTableComponent,
|
||||||
|
TwitchUsersModule,
|
||||||
],
|
],
|
||||||
templateUrl: './group-page.component.html',
|
templateUrl: './group-page.component.html',
|
||||||
styleUrl: './group-page.component.scss'
|
styleUrl: './group-page.component.scss'
|
||||||
@ -38,6 +41,7 @@ export class GroupPageComponent {
|
|||||||
private _chatters: GroupChatter[];
|
private _chatters: GroupChatter[];
|
||||||
private _policies: Policy[];
|
private _policies: Policy[];
|
||||||
|
|
||||||
|
isSpecialGroup = false;
|
||||||
groups: Group[] = [];
|
groups: Group[] = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -57,6 +61,7 @@ export class GroupPageComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._group = group;
|
this._group = group;
|
||||||
|
this.isSpecialGroup = SpecialGroups.includes(this.group!.name);
|
||||||
this._chatters = [...data['chatters'].filter((c: GroupChatter) => c.group_id == group_id)];
|
this._chatters = [...data['chatters'].filter((c: GroupChatter) => c.group_id == group_id)];
|
||||||
this._policies = [...data['policies'].filter((p: Policy) => p.group_id == group_id)];
|
this._policies = [...data['policies'].filter((p: Policy) => p.group_id == group_id)];
|
||||||
});
|
});
|
||||||
@ -79,7 +84,7 @@ export class GroupPageComponent {
|
|||||||
if (!this.group)
|
if (!this.group)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._client.first(d => d.d.request.type == 'delete_group' && d.d.request.data.id == this.group!.id)
|
this._client.first(d => d.d.request.type == 'delete_group' && d.d.request.data.group == this.group!.id)
|
||||||
.subscribe(async () => await this._router.navigate(['groups']));
|
.subscribe(async () => await this._router.navigate(['groups']));
|
||||||
this._client.deleteGroup(this.group.id);
|
this._client.deleteGroup(this.group.id);
|
||||||
}
|
}
|
||||||
|
@ -6,16 +6,12 @@
|
|||||||
<mat-menu #menu="matMenu">
|
<mat-menu #menu="matMenu">
|
||||||
<button mat-menu-item
|
<button mat-menu-item
|
||||||
(click)="openDialog('')">Custom Group</button>
|
(click)="openDialog('')">Custom Group</button>
|
||||||
|
@for (group of specialGroups; track $index) {
|
||||||
|
@if (!exists(group)) {
|
||||||
<button mat-menu-item
|
<button mat-menu-item
|
||||||
(click)="openDialog('everyone')">Everyone Group</button>
|
(click)="openDialog(group)">{{group[0].toUpperCase() + group.substring(1)}} Group</button>
|
||||||
<button mat-menu-item
|
}
|
||||||
(click)="openDialog('subscribers')">Subscriber Group</button>
|
}
|
||||||
<button mat-menu-item
|
|
||||||
(click)="openDialog('moderators')">Moderator Group</button>
|
|
||||||
<button mat-menu-item
|
|
||||||
(click)="openDialog('vip')">VIP Group</button>
|
|
||||||
<button mat-menu-item
|
|
||||||
(click)="openDialog('broadcaster')">Broadcaster Group</button>
|
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
<group-list class="groups"
|
<group-list class="groups"
|
||||||
[groups]="items" />
|
[groups]="items" />
|
@ -12,6 +12,7 @@ import { GroupItemEditComponent } from '../group-item-edit/group-item-edit.compo
|
|||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { HermesClientService } from '../../hermes-client.service';
|
import { HermesClientService } from '../../hermes-client.service';
|
||||||
import { GroupChatter } from '../../shared/models/group-chatter';
|
import { GroupChatter } from '../../shared/models/group-chatter';
|
||||||
|
import { SpecialGroups } from '../../shared/utils/groups';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'groups',
|
selector: 'groups',
|
||||||
@ -31,6 +32,8 @@ export class GroupsComponent {
|
|||||||
private readonly _client = inject(HermesClientService);
|
private readonly _client = inject(HermesClientService);
|
||||||
private readonly _route = inject(ActivatedRoute);
|
private readonly _route = inject(ActivatedRoute);
|
||||||
private readonly _dialog = inject(MatDialog);
|
private readonly _dialog = inject(MatDialog);
|
||||||
|
|
||||||
|
readonly specialGroups = SpecialGroups;
|
||||||
|
|
||||||
items: { group: Group, chatters: GroupChatter[], policies: Policy[] }[] = [];
|
items: { group: Group, chatters: GroupChatter[], policies: Policy[] }[] = [];
|
||||||
|
|
||||||
@ -116,4 +119,8 @@ export class GroupsComponent {
|
|||||||
compare(a: Group, b: Group) {
|
compare(a: Group, b: Group) {
|
||||||
return a.name.localeCompare(b.name);
|
return a.name.localeCompare(b.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exists(groupName: string) {
|
||||||
|
return this.items.some(g => g.group.name == groupName);
|
||||||
|
}
|
||||||
}
|
}
|
@ -14,10 +14,11 @@ export interface Message {
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class HermesClientService {
|
export class HermesClientService {
|
||||||
pipe = new DatePipe('en-US');
|
private readonly pipe = new DatePipe('en-US');
|
||||||
session_id: string | undefined;
|
session_id: string | undefined;
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
logged_in: boolean;
|
logged_in: boolean;
|
||||||
|
api_key: string | undefined;
|
||||||
|
|
||||||
constructor(private socket: HermesSocketService, private events: EventService) {
|
constructor(private socket: HermesSocketService, private events: EventService) {
|
||||||
this.connected = false;
|
this.connected = false;
|
||||||
@ -40,6 +41,7 @@ export class HermesClientService {
|
|||||||
this.connected = false;
|
this.connected = false;
|
||||||
this.logged_in = false;
|
this.logged_in = false;
|
||||||
this.session_id = undefined;
|
this.session_id = undefined;
|
||||||
|
this.api_key = undefined;
|
||||||
this.socket.close();
|
this.socket.close();
|
||||||
this.events.emit('tts_logoff', null);
|
this.events.emit('tts_logoff', null);
|
||||||
}
|
}
|
||||||
@ -78,6 +80,8 @@ export class HermesClientService {
|
|||||||
if (this.logged_in)
|
if (this.logged_in)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
this.api_key = api_key;
|
||||||
|
|
||||||
this.send(1, {
|
this.send(1, {
|
||||||
api_key,
|
api_key,
|
||||||
web_login: true,
|
web_login: true,
|
||||||
@ -97,6 +101,17 @@ export class HermesClientService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public createGroupChatter(groupId: string, chatterId: string, chatterLabel: string) {
|
||||||
|
if (!this.logged_in)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.send(3, {
|
||||||
|
request_id: null,
|
||||||
|
type: "create_group_chatter",
|
||||||
|
data: { group: groupId, chatter: chatterId, label: chatterLabel },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public createPolicy(groupId: string, path: string, usage: number, timespan: number) {
|
public createPolicy(groupId: string, path: string, usage: number, timespan: number) {
|
||||||
if (!this.logged_in)
|
if (!this.logged_in)
|
||||||
return;
|
return;
|
||||||
@ -157,6 +172,17 @@ export class HermesClientService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public deleteGroupChatter(groupId: string, chatterId: string) {
|
||||||
|
if (!this.logged_in)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.send(3, {
|
||||||
|
request_id: null,
|
||||||
|
type: "delete_group_chatter",
|
||||||
|
data: { group: groupId, chatter: chatterId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public deletePolicy(id: string) {
|
public deletePolicy(id: string) {
|
||||||
if (!this.logged_in)
|
if (!this.logged_in)
|
||||||
return;
|
return;
|
||||||
@ -363,6 +389,9 @@ export class HermesClientService {
|
|||||||
console.log("TTS Heartbeat received. Potential connection problem?");
|
console.log("TTS Heartbeat received. Potential connection problem?");
|
||||||
break;
|
break;
|
||||||
case 2: // Login Ack
|
case 2: // Login Ack
|
||||||
|
if (message.d.another_client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.log("TTS Login successful.");
|
console.log("TTS Login successful.");
|
||||||
this.logged_in = true;
|
this.logged_in = true;
|
||||||
this.session_id = message.d.session_id;
|
this.session_id = message.d.session_id;
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
}
|
}
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions>
|
<mat-card-actions align="end">
|
||||||
@if (isNew) {
|
@if (isNew) {
|
||||||
<button mat-button
|
<button mat-button
|
||||||
(click)="save()">
|
(click)="save()">
|
||||||
|
@ -89,7 +89,13 @@ export class PolicyItemEditComponent implements OnInit, AfterViewInit {
|
|||||||
if (this.isNew) {
|
if (this.isNew) {
|
||||||
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'create_policy' && d.d.data.group_id == group_id && d.d.data.path == path && d.d.data.usage == usage && d.d.data.span == span)
|
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'create_policy' && d.d.data.group_id == group_id && d.d.data.path == path && d.d.data.usage == usage && d.d.data.span == span)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (d) => this.dialogRef.close(d.d.data),
|
next: (d) => {
|
||||||
|
if (d.d.error) {
|
||||||
|
// TODO: update & show response error message.
|
||||||
|
} else {
|
||||||
|
this.dialogRef.close(d.d.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
error: () => this.waitForResponse = false,
|
error: () => this.waitForResponse = false,
|
||||||
complete: () => this.waitForResponse = false,
|
complete: () => this.waitForResponse = false,
|
||||||
});
|
});
|
||||||
@ -97,7 +103,13 @@ export class PolicyItemEditComponent implements OnInit, AfterViewInit {
|
|||||||
} else {
|
} else {
|
||||||
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'update_policy' && d.d.data.id == this.data.policy_id && d.d.data.group_id == group_id && d.d.data.path == path && d.d.data.usage == usage && d.d.data.span == span)
|
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'update_policy' && d.d.data.id == this.data.policy_id && d.d.data.group_id == group_id && d.d.data.path == path && d.d.data.usage == usage && d.d.data.span == span)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (d) => this.dialogRef.close(d.d.data),
|
next: (d) => {
|
||||||
|
if (d.d.error) {
|
||||||
|
// TODO: update & show response error message.
|
||||||
|
} else {
|
||||||
|
this.dialogRef.close(d.d.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
error: () => this.waitForResponse = false,
|
error: () => this.waitForResponse = false,
|
||||||
complete: () => this.waitForResponse = false,
|
complete: () => this.waitForResponse = false,
|
||||||
});
|
});
|
||||||
|
@ -91,10 +91,9 @@ export class RedemptionItemEditComponent implements OnInit {
|
|||||||
next: (d) => {
|
next: (d) => {
|
||||||
if (d.d.error) {
|
if (d.d.error) {
|
||||||
this.responseError = d.d.error;
|
this.responseError = d.d.error;
|
||||||
return;
|
} else {
|
||||||
|
this.dialogRef.close(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dialogRef.close(id);
|
|
||||||
},
|
},
|
||||||
error: () => { this.responseError = 'Failed to receive response back from server.'; this.waitForResponse = false; },
|
error: () => { this.responseError = 'Failed to receive response back from server.'; this.waitForResponse = false; },
|
||||||
complete: () => this.waitForResponse = false,
|
complete: () => this.waitForResponse = false,
|
||||||
@ -123,11 +122,10 @@ export class RedemptionItemEditComponent implements OnInit {
|
|||||||
next: (d) => {
|
next: (d) => {
|
||||||
if (d.d.error) {
|
if (d.d.error) {
|
||||||
this.responseError = d.d.error;
|
this.responseError = d.d.error;
|
||||||
return;
|
} else {
|
||||||
|
this.redemption.order = order;
|
||||||
|
this.dialogRef.close(d.d.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.redemption.order = order;
|
|
||||||
this.dialogRef.close(d.d.data);
|
|
||||||
},
|
},
|
||||||
error: () => { this.responseError = 'Failed to receive response back from server.'; this.waitForResponse = false; },
|
error: () => { this.responseError = 'Failed to receive response back from server.'; this.waitForResponse = false; },
|
||||||
complete: () => this.waitForResponse = false,
|
complete: () => this.waitForResponse = false,
|
||||||
@ -139,10 +137,10 @@ export class RedemptionItemEditComponent implements OnInit {
|
|||||||
next: (d) => {
|
next: (d) => {
|
||||||
if (d.d.error) {
|
if (d.d.error) {
|
||||||
this.responseError = d.d.error;
|
this.responseError = d.d.error;
|
||||||
return;
|
} else {
|
||||||
|
this.redemption.order = order;
|
||||||
|
this.dialogRef.close(d.d.data);
|
||||||
}
|
}
|
||||||
this.redemption.order = order;
|
|
||||||
this.dialogRef.close(d.d.data);
|
|
||||||
},
|
},
|
||||||
error: () => { this.responseError = 'Failed to receive response back from server.'; this.waitForResponse = false; },
|
error: () => { this.responseError = 'Failed to receive response back from server.'; this.waitForResponse = false; },
|
||||||
complete: () => this.waitForResponse = false,
|
complete: () => this.waitForResponse = false,
|
||||||
|
1
src/app/shared/utils/groups.ts
Normal file
1
src/app/shared/utils/groups.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const SpecialGroups = ['everyone', 'subscribers', 'moderators', 'vip', 'broadcaster'];
|
17
src/app/shared/utils/string-compare.ts
Normal file
17
src/app/shared/utils/string-compare.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export function containsLettersInOrder(value: string, inside: string): boolean {
|
||||||
|
return containsLettersInOrderInternal(value, inside, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function containsLettersInOrderInternal(value: string, inside: string, indexValue: number, indexInside: number): boolean {
|
||||||
|
if (indexInside >= inside.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (indexValue >= value.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = value.at(indexValue) == inside.at(indexInside);
|
||||||
|
return containsLettersInOrderInternal(value, inside, indexValue + 1, indexInside + (match ? 1 : 0));
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
<mat-card>
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title-group>
|
||||||
|
<mat-card-title>Add Twitch User to Group</mat-card-title>
|
||||||
|
<mat-card-subtitle>Adding to ...</mat-card-subtitle>
|
||||||
|
</mat-card-title-group>
|
||||||
|
</mat-card-header>
|
||||||
|
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput
|
||||||
|
[formControl]="usernameControl" />
|
||||||
|
</mat-form-field>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-card-actions class="actions">
|
||||||
|
<button mat-raised-button
|
||||||
|
(click)="dialogRef.close()">Cancel</button>
|
||||||
|
<button mat-raised-button
|
||||||
|
disabled="{{waitForResponse}}"
|
||||||
|
(click)="submit()">Add</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { TwitchUserItemAddComponent } from './twitch-user-item-add.component';
|
||||||
|
|
||||||
|
describe('TwitchUserItemAddComponent', () => {
|
||||||
|
let component: TwitchUserItemAddComponent;
|
||||||
|
let fixture: ComponentFixture<TwitchUserItemAddComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [TwitchUserItemAddComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(TwitchUserItemAddComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,81 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Component, inject, OnInit } from '@angular/core';
|
||||||
|
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { ActionItemEditComponent } from '../../actions/action-item-edit/action-item-edit.component';
|
||||||
|
import { HermesClientService } from '../../hermes-client.service';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { Group } from '../../shared/models/group';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { group } from 'console';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-twitch-user-item-add',
|
||||||
|
imports: [
|
||||||
|
MatButtonModule,
|
||||||
|
MatCardModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatInputModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
],
|
||||||
|
templateUrl: './twitch-user-item-add.component.html',
|
||||||
|
styleUrl: './twitch-user-item-add.component.scss'
|
||||||
|
})
|
||||||
|
export class TwitchUserItemAddComponent implements OnInit {
|
||||||
|
private readonly client = inject(HermesClientService);
|
||||||
|
private readonly data = inject<{ username: string, group: Group }>(MAT_DIALOG_DATA);
|
||||||
|
private readonly http = inject(HttpClient);
|
||||||
|
|
||||||
|
readonly usernameControl = new FormControl('', [Validators.required]);
|
||||||
|
readonly dialogRef = inject(MatDialogRef<ActionItemEditComponent>);
|
||||||
|
|
||||||
|
waitForResponse = false;
|
||||||
|
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.usernameControl.setValue(this.data.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
if (this.usernameControl.invalid || this.waitForResponse || !this.client.api_key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.waitForResponse = true;
|
||||||
|
|
||||||
|
const username = this.usernameControl.value!.toLowerCase();
|
||||||
|
this.http.get('/api/auth/twitch/users?login=' + username, {
|
||||||
|
headers: {
|
||||||
|
'x-api-key': this.client.api_key,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.subscribe((response: any) => {
|
||||||
|
if (!response.user) {
|
||||||
|
this.waitForResponse = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'create_group_chatter' && d.d.request.data.chatter == response.user.id)
|
||||||
|
.subscribe({
|
||||||
|
next: (d) => {
|
||||||
|
if (d.d.error) {
|
||||||
|
// TODO: update & show response error message.
|
||||||
|
} else {
|
||||||
|
this.dialogRef.close(d.d.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: () => this.waitForResponse = false,
|
||||||
|
complete: () => this.waitForResponse = false,
|
||||||
|
});
|
||||||
|
this.client.createGroupChatter(this.data.group.id, response.user.id, response.user.login)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
<div>
|
||||||
|
<button mat-icon-button
|
||||||
|
(click)="delete()">
|
||||||
|
<mat-icon>remove</mat-icon>
|
||||||
|
</button>
|
||||||
|
<p>{{user.chatter_label}}</p>
|
||||||
|
</div>
|
@ -0,0 +1,18 @@
|
|||||||
|
div {
|
||||||
|
padding: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
position: relative;
|
||||||
|
top: -7px;
|
||||||
|
margin-left: 5px;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-icon {
|
||||||
|
color: #C83838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-icon:hover {
|
||||||
|
color: red;
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { TwitchUserItemComponent } from './twitch-user-item.component';
|
||||||
|
|
||||||
|
describe('TwitchUserItemComponent', () => {
|
||||||
|
let component: TwitchUserItemComponent;
|
||||||
|
let fixture: ComponentFixture<TwitchUserItemComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [TwitchUserItemComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(TwitchUserItemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,39 @@
|
|||||||
|
import { Component, inject, Input } from '@angular/core';
|
||||||
|
import { GroupChatter } from '../../shared/models/group-chatter';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { HermesClientService } from '../../hermes-client.service';
|
||||||
|
import EventService from '../../shared/services/EventService';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'twitch-user-item',
|
||||||
|
imports: [
|
||||||
|
MatButtonModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatInputModule
|
||||||
|
],
|
||||||
|
templateUrl: './twitch-user-item.component.html',
|
||||||
|
styleUrl: './twitch-user-item.component.scss'
|
||||||
|
})
|
||||||
|
export class TwitchUserItemComponent {
|
||||||
|
@Input({ required: true }) user: GroupChatter = { chatter_id: -1, chatter_label: '', user_id: '', group_id: '' };
|
||||||
|
|
||||||
|
private readonly _client = inject(HermesClientService);
|
||||||
|
private readonly _events = inject(EventService);
|
||||||
|
private _deleted = false;
|
||||||
|
|
||||||
|
delete() {
|
||||||
|
if (this._deleted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._deleted = true;
|
||||||
|
|
||||||
|
this._client.first(d => d.d.request.type == 'delete_group_chatter' && d.d.request.data.group == this.user.group_id && d.d.request.data.chatter == this.user.chatter_id)
|
||||||
|
.subscribe(async (response) => {
|
||||||
|
console.log('delete group chatter', response)
|
||||||
|
this._events.emit('delete_group_chatter', this.user);
|
||||||
|
});
|
||||||
|
this._client.deleteGroupChatter(this.user.group_id, this.user.chatter_id.toString());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
<ul>
|
||||||
|
<li class="header">
|
||||||
|
<mat-form-field appearance="outline"
|
||||||
|
subscriptSizing="dynamic">
|
||||||
|
<mat-label>Filter</mat-label>
|
||||||
|
<input matInput
|
||||||
|
placeholder="Filter Twitch usernames"
|
||||||
|
[formControl]="searchControl" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<button mat-icon-button
|
||||||
|
(click)="add()">
|
||||||
|
<mat-icon>person_add</mat-icon>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
@for (user of users; track $index) {
|
||||||
|
<li>
|
||||||
|
<twitch-user-item [user]="user" />
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
@if (!users.length) {
|
||||||
|
@if (searchControl.value) {
|
||||||
|
<p class="notice">No users fits the filter.</p>
|
||||||
|
} @else {
|
||||||
|
<p class="notice">No users in this group.</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</ul>
|
@ -0,0 +1,38 @@
|
|||||||
|
@use '@angular/material' as mat;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
@include mat.all-component-densities(-5);
|
||||||
|
|
||||||
|
@include mat.form-field-overrides((
|
||||||
|
outlined-outline-color: rgb(167, 88, 199),
|
||||||
|
outlined-focus-label-text-color: rgb(155, 57, 194),
|
||||||
|
outlined-focus-outline-color: rgb(155, 57, 194),
|
||||||
|
));
|
||||||
|
|
||||||
|
background-color: rgb(202, 68, 255);
|
||||||
|
border-radius: 15px;
|
||||||
|
margin: 0 0;
|
||||||
|
padding: 0;
|
||||||
|
max-width: 500px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
background-color: rgb(240, 165, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li.header {
|
||||||
|
background-color: rgb(215, 115, 255);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul .notice {
|
||||||
|
text-align: center;
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { TwitchUserListComponent } from './twitch-user-list.component';
|
||||||
|
|
||||||
|
describe('TwitchUserListComponent', () => {
|
||||||
|
let component: TwitchUserListComponent;
|
||||||
|
let fixture: ComponentFixture<TwitchUserListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [TwitchUserListComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(TwitchUserListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,68 @@
|
|||||||
|
import { Component, inject, Input } from '@angular/core';
|
||||||
|
import { GroupChatter } from '../../shared/models/group-chatter';
|
||||||
|
import { TwitchUserItemComponent } from "../twitch-user-item/twitch-user-item.component";
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { containsLettersInOrder } from '../../shared/utils/string-compare';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { HermesClientService } from '../../hermes-client.service';
|
||||||
|
import { Group } from '../../shared/models/group';
|
||||||
|
import { TwitchUserItemAddComponent } from '../twitch-user-item-add/twitch-user-item-add.component';
|
||||||
|
import EventService from '../../shared/services/EventService';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'twitch-user-list',
|
||||||
|
imports: [
|
||||||
|
MatButtonModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatInputModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TwitchUserItemComponent,
|
||||||
|
],
|
||||||
|
templateUrl: './twitch-user-list.component.html',
|
||||||
|
styleUrl: './twitch-user-list.component.scss'
|
||||||
|
})
|
||||||
|
export class TwitchUserListComponent {
|
||||||
|
@Input({ required: true }) twitchUsers: GroupChatter[] = [];
|
||||||
|
@Input() group: Group | undefined;
|
||||||
|
|
||||||
|
readonly dialog = inject(MatDialog);
|
||||||
|
readonly client = inject(HermesClientService);
|
||||||
|
readonly events = inject(EventService);
|
||||||
|
readonly searchControl: FormControl = new FormControl('');
|
||||||
|
|
||||||
|
opened = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.events.listen('delete_group_chatter', (chatter: GroupChatter) => {
|
||||||
|
this.twitchUsers.splice(this.twitchUsers.findIndex(c => c.group_id == chatter.group_id && c.chatter_id == chatter.chatter_id), 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get users(): GroupChatter[] {
|
||||||
|
return this.twitchUsers.filter(u => containsLettersInOrder(u.chatter_label, this.searchControl.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
add() {
|
||||||
|
if (this.opened)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.opened = true;
|
||||||
|
|
||||||
|
const dialogRef = this.dialog.open(TwitchUserItemAddComponent, {
|
||||||
|
data: { username: this.searchControl.value, group: this.group },
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe((chatter: GroupChatter) => {
|
||||||
|
this.opened = false;
|
||||||
|
if (!chatter)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.twitchUsers.push(chatter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
19
src/app/twitch-users/twitch-users.module.ts
Normal file
19
src/app/twitch-users/twitch-users.module.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { TwitchUserItemComponent } from './twitch-user-item/twitch-user-item.component';
|
||||||
|
import { TwitchUserListComponent } from './twitch-user-list/twitch-user-list.component';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [],
|
||||||
|
exports: [
|
||||||
|
TwitchUserItemComponent,
|
||||||
|
TwitchUserListComponent,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
TwitchUserItemComponent,
|
||||||
|
TwitchUserListComponent,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class TwitchUsersModule { }
|
1
src/app/users/user-item/user-item.component.html
Normal file
1
src/app/users/user-item/user-item.component.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<p>user-item works!</p>
|
0
src/app/users/user-item/user-item.component.scss
Normal file
0
src/app/users/user-item/user-item.component.scss
Normal file
23
src/app/users/user-item/user-item.component.spec.ts
Normal file
23
src/app/users/user-item/user-item.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UserItemComponent } from './user-item.component';
|
||||||
|
|
||||||
|
describe('UserItemComponent', () => {
|
||||||
|
let component: UserItemComponent;
|
||||||
|
let fixture: ComponentFixture<UserItemComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [UserItemComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(UserItemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
11
src/app/users/user-item/user-item.component.ts
Normal file
11
src/app/users/user-item/user-item.component.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-user-item',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './user-item.component.html',
|
||||||
|
styleUrl: './user-item.component.scss'
|
||||||
|
})
|
||||||
|
export class UserItemComponent {
|
||||||
|
|
||||||
|
}
|
1
src/app/users/user-list/user-list.component.html
Normal file
1
src/app/users/user-list/user-list.component.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<p>user-list works!</p>
|
0
src/app/users/user-list/user-list.component.scss
Normal file
0
src/app/users/user-list/user-list.component.scss
Normal file
23
src/app/users/user-list/user-list.component.spec.ts
Normal file
23
src/app/users/user-list/user-list.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UserListComponent } from './user-list.component';
|
||||||
|
|
||||||
|
describe('UserListComponent', () => {
|
||||||
|
let component: UserListComponent;
|
||||||
|
let fixture: ComponentFixture<UserListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [UserListComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(UserListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
11
src/app/users/user-list/user-list.component.ts
Normal file
11
src/app/users/user-list/user-list.component.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-user-list',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './user-list.component.html',
|
||||||
|
styleUrl: './user-list.component.scss'
|
||||||
|
})
|
||||||
|
export class UserListComponent {
|
||||||
|
|
||||||
|
}
|
12
src/app/users/users.module.ts
Normal file
12
src/app/users/users.module.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [],
|
||||||
|
imports: [
|
||||||
|
CommonModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class UsersModule { }
|
Loading…
x
Reference in New Issue
Block a user