Auto-formatted every file to keep everything consistent.

This commit is contained in:
Tom
2025-03-18 14:03:07 +00:00
parent 74b282ccfd
commit 9201f9b6c5
91 changed files with 14891 additions and 14767 deletions

2
package-lock.json generated
View File

@ -14798,5 +14798,5 @@
"integrity": "sha512-9oxn0IIjbCZkJ67L+LkhYWRyAy7axphb3VgE2MBDlOqnmHMPWGYMxJxBYFueFq/JGY2GMwS0rU+UCLunEmy5UA==",
"license": "MIT"
}
}
}
}

View File

@ -1,7 +1,6 @@
<mat-form-field>
<mat-label>Redeemable Action</mat-label>
<input
matInput
<input matInput
type="text"
placeholder="Pick a Redeemable Action"
aria-label="redeemable action"
@ -9,7 +8,9 @@
[matAutocomplete]="auto"
(blur)="blur()"
(input)="input()">
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn" (optionSelected)="select($event.option.value)">
<mat-autocomplete #auto="matAutocomplete"
[displayWith]="displayFn"
(optionSelected)="select($event.option.value)">
@for (action of filteredActions; track action.name) {
<mat-option [value]="action">{{action.name}}</mat-option>
}

View File

@ -8,11 +8,14 @@
</mat-card-header>
<mat-card-content>
<form class="grid" [formGroup]="formGroup">
<form class="grid"
[formGroup]="formGroup">
<div class="item">
<mat-form-field>
<mat-label>Redeemable Action Name</mat-label>
<input matInput type="text" formControlName="name">
<input matInput
type="text"
formControlName="name">
@if (isNew && formGroup.get('name')?.invalid && (formGroup.get('name')?.dirty ||
formGroup.get('name')?.touched)) {
@if (formGroup.get('name')?.hasError('required')) {
@ -27,7 +30,9 @@
<div class="item">
<mat-form-field>
<mat-label>Type</mat-label>
<mat-select matInput formControlName="type" (selectionChange)="action.type = $event.value">
<mat-select matInput
formControlName="type"
(selectionChange)="action.type = $event.value">
@for (type of actionTypes; track $index) {
<mat-option value="{{type}}">{{type}}</mat-option>
}
@ -50,7 +55,10 @@
@if (field.type == 'text') {
<mat-form-field>
<mat-label>{{field.label}}</mat-label>
<input matInput type="text" placeholder="{{field.placeholder}}" [formControl]="field.control"
<input matInput
type="text"
placeholder="{{field.placeholder}}"
[formControl]="field.control"
[(ngModel)]="action.data[field.key]">
@if (field.control.invalid && (field.control.dirty || field.control.touched)) {
@if (field.control.hasError('required')) {
@ -65,7 +73,9 @@
@else if (field.type == 'number') {
<mat-form-field>
<mat-label>{{field.label}}</mat-label>
<input matInput type="number" [formControl]="field.control">
<input matInput
type="number"
[formControl]="field.control">
@if (field.control.invalid && (field.control.dirty || field.control.touched)) {
@if (field.control.hasError('required')) {
<small class="error">This field is required.</small>
@ -98,15 +108,16 @@
}
</mat-card-content>
<mat-card-actions class="actions" align="end">
<mat-card-actions class="actions"
align="end">
@if (!isNew) {
<button mat-raised-button class="delete" (click)="deleteAction(action)">Delete</button>
<button mat-raised-button
class="delete"
(click)="deleteAction(action)">Delete</button>
}
<button
mat-raised-button
<button mat-raised-button
(click)="dialogRef.close()">Cancel</button>
<button
mat-raised-button
<button mat-raised-button
disabled="{{!formsDirty || !formsValidity || waitForResponse}}"
(click)="save()">Save</button>
</mat-card-actions>

View File

@ -25,6 +25,6 @@
color: #ba1a1a;
}
.mdc-button ~ .mdc-button {
.mdc-button~.mdc-button {
margin-left: 1em;
}

View File

@ -1,11 +1,15 @@
<main>
@for (action of actions; track $index) {
<button type="button" class="container" (click)="modify(action)">
<button type="button"
class="container"
(click)="modify(action)">
<span class="title">{{action.name}}</span>
<span class="subtitle">{{action.type}}</span>
</button>
}
<button type="button" class="container" (click)="create()">
<button type="button"
class="container"
(click)="create()">
<mat-icon>add</mat-icon>
</button>
</main>

View File

@ -5,7 +5,8 @@
<article>
<mat-form-field>
<mat-label>Filter by type</mat-label>
<mat-select (selectionChange)="onFilterChange($event.value)" value="0">
<mat-select (selectionChange)="onFilterChange($event.value)"
value="0">
<mat-select-trigger>
<mat-icon matPrefix>filter_list</mat-icon>&nbsp;{{filter.name}}
</mat-select-trigger>
@ -27,5 +28,7 @@
</mat-form-field>
</article>
</section>
<action-list class="center" [actions]="actions" (actionsChange)="items.push($event)" />
<action-list class="center"
[actions]="actions"
(actionsChange)="items.push($event)" />
</body>

View File

@ -1,4 +1,5 @@
body, h3 {
body,
h3 {
padding: 0;
margin: 0;
}
@ -28,7 +29,7 @@ section {
article {
display: flex;
justify-content:space-around;
justify-content: space-around;
}
}

View File

@ -2,7 +2,8 @@
<main>
<mat-form-field>
<mat-label>User to impersonate</mat-label>
<mat-select (selectionChange)="onChange($event)" [(value)]="impersonated">
<mat-select (selectionChange)="onChange($event)"
[(value)]="impersonated">
<mat-option>{{getUsername()}}</mat-option>
@for (user of users; track user.id) {
<mat-option [value]="user.id">{{ user.name }}</mat-option>

View File

@ -1,5 +1,6 @@
<div class="login">
<mat-card class="outer" appearance="outlined">
<mat-card class="outer"
appearance="outlined">
<mat-card-header>
<h1 class="title">Login</h1>
</mat-card-header>
@ -8,7 +9,9 @@
<p>Log in with your favorite livestream service</p>
<a>
<mat-card appearance="outlined" class="twitch" (click)="login()">
<mat-card appearance="outlined"
class="twitch"
(click)="login()">
<mat-card-content>
Twitch
</mat-card-content>

View File

@ -17,7 +17,8 @@
</mat-form-field>
</mat-card-content>
<mat-card-actions align="end">
<button mat-raised-button (click)="login()">Log In</button>
<button mat-raised-button
(click)="login()">Log In</button>
</mat-card-actions>
</mat-card>
</main>

View File

@ -1,6 +1,7 @@
@if (auth.isAuthenticated()) {
<main>
<mat-card appearance="outlined" class="card">
<mat-card appearance="outlined"
class="card">
<mat-card-header>
<mat-card-title>{{username}}</mat-card-title>
</mat-card-header>
@ -10,9 +11,11 @@
<mat-card-actions class="actions">
<div>
@if (isTTSLoggedIn) {
<button mat-raised-button (click)="client.disconnect()"><span class="disconnect">Disconnect</span></button>
<button mat-raised-button
(click)="client.disconnect()"><span class="disconnect">Disconnect</span></button>
}
<button mat-raised-button (click)="auth.logout()"><span class="logoff">Log Off</span></button>
<button mat-raised-button
(click)="auth.logout()"><span class="logoff">Log Off</span></button>
</div>
</mat-card-actions>
</mat-card>

View File

@ -14,10 +14,11 @@ main {
justify-content: center;
}
.disconnect, .logoff {
.disconnect,
.logoff {
color: red;
}
.mdc-button ~ .mdc-button {
.mdc-button~.mdc-button {
margin-left: 1em;
}

View File

@ -1,7 +1,6 @@
<mat-form-field>
<mat-label>Group</mat-label>
<input
matInput
<input matInput
type="text"
placeholder="Pick a group"
aria-label="group"
@ -11,7 +10,9 @@
[readonly]="!!groupDisabled"
(blur)="blur()"
(input)="input()">
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn" (optionSelected)="select($event.option.value)">
<mat-autocomplete #auto="matAutocomplete"
[displayWith]="displayFn"
(optionSelected)="select($event.option.value)">
@for (group of filteredGroups; track group.id) {
<mat-option [value]="group">{{group.name}}</mat-option>
}

View File

@ -7,7 +7,10 @@
<mat-card-content>
<mat-form-field>
<mat-label>Group Name</mat-label>
<input matInput type="text" [formControl]="nameForm" [disabled]="isSpecial" />
<input matInput
type="text"
[formControl]="nameForm"
[disabled]="isSpecial" />
@if (nameForm.invalid && (nameForm.dirty || nameForm.touched)) {
@if (nameForm.hasError('required')) {
<small class="error">This field is required.</small>
@ -16,7 +19,9 @@
</mat-form-field>
<mat-form-field>
<mat-label>TTS Priority</mat-label>
<input matInput type="number" [formControl]="priorityForm" />
<input matInput
type="number"
[formControl]="priorityForm" />
@if (priorityForm.invalid && (priorityForm.dirty || priorityForm.touched)) {
@if (priorityForm.hasError('required')) {
<small class="error">This field is required.</small>
@ -34,14 +39,12 @@
</mat-form-field>
</mat-card-content>
<mat-card-actions>
<button
mat-button
<button mat-button
[disabled]="waitForResponse || formGroup.invalid"
(click)="add()">
<mat-icon>add</mat-icon>Add
</button>
<button
mat-button
<button mat-button
[disabled]="waitForResponse"
(click)="cancel()">
<mat-icon>cancel</mat-icon>Cancel

View File

@ -14,7 +14,8 @@
<small class="muted block">polic{{item().chatters.length == 1 ? 'y' : 'ies'}}</small>
</section>
<section>
<button mat-button (click)="router.navigate([link])">
<button mat-button
(click)="router.navigate([link])">
<mat-icon>pageview</mat-icon>View
</button>
</section>

View File

@ -1,7 +1,7 @@
<ul>
@for (group of groups; track $index) {
@for (group of groups; track $index) {
<li>
<group-item [item]="group" />
</li>
}
}
</ul>

View File

@ -8,7 +8,10 @@
{{policies.length}} polic{{policies.length == 1 ? 'y' : 'ies'}}
</mat-panel-description>
</mat-expansion-panel-header>
<policy-add-button class="add" [groups]="groups" [policies]="policies" [group]="group?.id" />
<policy-add-button class="add"
[groups]="groups"
[policies]="policies"
[group]="group?.id" />
@if (policies.length > 0) {
<policy-table [policies]="policies" />
}
@ -28,7 +31,9 @@
<p>Deleting this group will delete everything that is part of it, including policies and permissions.</p>
</article>
<article class="right">
<button mat-raised-button class="delete" (click)="delete()">
<button mat-raised-button
class="delete"
(click)="delete()">
<mat-icon>delete</mat-icon>Delete this group.
</button>
</article>

View File

@ -1,4 +1,4 @@
.mat-expansion-panel ~ .mat-expansion-panel {
.mat-expansion-panel~.mat-expansion-panel {
margin-top: 4em;
}

View File

@ -1,15 +1,21 @@
<button mat-button [mat-menu-trigger-for]="menu">
<button mat-button
[mat-menu-trigger-for]="menu">
<mat-icon>add</mat-icon>
Add a group
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="openDialog('')">Custom Group</button>
<button mat-menu-item (click)="openDialog('everyone')">Everyone 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>
<button mat-menu-item
(click)="openDialog('')">Custom Group</button>
<button mat-menu-item
(click)="openDialog('everyone')">Everyone 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>
<group-list
class="groups"
<group-list class="groups"
[groups]="items" />

View File

@ -44,14 +44,14 @@ export class HermesClientService {
this.events.emit('tts_logoff', null);
}
public filter(predicate: (data: any) => boolean): Observable<any>|undefined {
public filter(predicate: (data: any) => boolean): Observable<any> | undefined {
return this.socket.get$()?.pipe(
filter(predicate),
map(d => d.d)
);
}
public filterByRequestType(requestName: string): Observable<any>|undefined {
public filterByRequestType(requestName: string): Observable<any> | undefined {
return this.socket.get$()?.pipe(
filter(d => d.op == 4 && d.d.request.type === requestName),
map(d => d.d)

View File

@ -3,42 +3,49 @@
<ul>
@if (!isLoggedIn()) {
<li>
<a routerLink="/login" routerLinkActive="active">
<a routerLink="/login"
routerLinkActive="active">
Login
</a>
</li>
}
@if (isLoggedIn() && !isTTSLoggedIn()) {
<li>
<a routerLink="/tts-login" routerLinkActive="active">
<a routerLink="/tts-login"
routerLinkActive="active">
TTS Login
</a>
</li>
}
@if (isLoggedIn() && isTTSLoggedIn()) {
<li>
<a routerLink="/policies" routerLinkActive="active">
<a routerLink="/policies"
routerLinkActive="active">
Policies
</a>
</li>
<li>
<a routerLink="/filters" routerLinkActive="active">
<a routerLink="/filters"
routerLinkActive="active">
Filters
</a>
</li>
<li>
<a routerLink="/actions" routerLinkActive="active">
<a routerLink="/actions"
routerLinkActive="active">
Actions
</a>
</li>
<li>
<a routerLink="/redemptions" routerLinkActive="active">
<a routerLink="/redemptions"
routerLinkActive="active">
Redemptions
</a>
</li>
@if (isAdmin()) {
<li>
<a routerLink="/groups" routerLinkActive="active">
<a routerLink="/groups"
routerLinkActive="active">
Groups
</a>
</li>

View File

@ -3,6 +3,7 @@ import { PolicyComponent } from './policy/policy.component';
import { PolicyTableComponent } from './policy-table/policy-table.component';
import { PolicyItemEditComponent } from './policy-item-edit/policy-item-edit.component';
import { PolicyAddButtonComponent } from './policy-add-button/policy-add-button.component';
import { PolicyAddFormComponent } from './policy-add-form/policy-add-form.component';
@NgModule({
@ -12,6 +13,7 @@ import { PolicyAddButtonComponent } from './policy-add-button/policy-add-button.
PolicyTableComponent,
PolicyAddButtonComponent,
PolicyItemEditComponent,
PolicyAddFormComponent,
]
})
export class PoliciesModule { }

View File

@ -1,4 +1,5 @@
<button mat-button (click)="openDialog()">
<button mat-button
(click)="openDialog()">
<mat-icon>add</mat-icon>
Add a policy
</button>

View File

@ -19,7 +19,7 @@ export class PolicyAddButtonComponent {
private readonly dialog = inject(MatDialog);
@Input({ required: true }) policies: Policy[] = [];
@Input({ required: true }) groups: Group[] = [];
@Input() group: string|undefined = undefined;
@Input() group: string | undefined = undefined;
@Output() policy = new EventEmitter<Policy>();

View File

@ -1,14 +1,12 @@
<div>
<form
standalone>
<form standalone>
<mat-form-field>
<input
name="path"
<mat-label>Path</mat-label>
<input name="path"
type="text"
placeholder="Pick one"
[(ngModel)]="newPolicyName"
[(ngModel)]="policy"
matInput
[formControl]="myControl"
[formControl]="policyControl"
[matAutocomplete]="auto" />
<mat-autocomplete #auto="matAutocomplete">
@for (option of filteredPolicies | async; track option) {
@ -16,10 +14,4 @@
}
</mat-autocomplete>
</mat-form-field>
<button
mat-flat-button
(click)="addNewPolicy()">
Add
</button>
</form>
</div>
</form>

View File

@ -4,9 +4,7 @@ import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import EventService from '../../shared/services/EventService';
import { map, Observable, startWith } from 'rxjs';
import { HermesClientService } from '../../hermes-client.service';
const Policies = [
{ path: "tts", description: "Anything to do with TTS" },
@ -43,19 +41,19 @@ const Policies = [
styleUrl: './policy-add-form.component.scss'
})
export class PolicyAddFormComponent {
myControl = new FormControl('');
newPolicyName: string = '';
policyControl = new FormControl('');
policy: string = '';
filteredPolicies: Observable<string[]>;
constructor(private events: EventService, private hermes: HermesClientService) {
this.filteredPolicies = this.myControl.valueChanges.pipe(
constructor() {
this.filteredPolicies = this.policyControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value || '')),
);
}
ngOnInit() {
this.filteredPolicies = this.myControl.valueChanges.pipe(
this.filteredPolicies = this.policyControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value || '')),
);
@ -63,12 +61,11 @@ export class PolicyAddFormComponent {
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return Policies.map(p => p.path).filter(option => option.toLowerCase().includes(filterValue));
const names = Policies.map(p => p.path);
if (names.includes(filterValue)) {
return names;
}
addNewPolicy() {
this.events.emit('addPolicy', this.newPolicyName);
this.newPolicyName = "";
return names.filter(option => option.toLowerCase().includes(filterValue));
}
}

View File

@ -3,8 +3,7 @@
<mat-card-title>{{isNew ? 'Add' : 'Edit'}} Policy</mat-card-title>
</mat-card-header>
<mat-card-content>
<group-dropdown
ngDefaultControl
<group-dropdown ngDefaultControl
[formControl]="groupControl"
[groups]="data.groups"
[group]="data.group_id"
@ -12,7 +11,9 @@
[errorMessages]="groupErrorMessages" />
<mat-form-field>
<mat-label>Path</mat-label>
<input matInput placeholder="Path" [formControl]="pathControl" />
<input matInput
placeholder="Path"
[formControl]="pathControl" />
@if (pathControl.invalid && (pathControl.dirty || pathControl.touched)) {
@if (pathControl.hasError('required')) {
<small class="error">This field is required.</small>
@ -21,7 +22,9 @@
</mat-form-field>
<mat-form-field>
<mat-label>Usage</mat-label>
<input matInput type="number" [formControl]="usageControl" />
<input matInput
type="number"
[formControl]="usageControl" />
@if (usageControl.invalid && (usageControl.dirty || usageControl.touched)) {
@if (usageControl.hasError('required')) {
<small class="error">This field is required.</small>
@ -39,7 +42,9 @@
</mat-form-field>
<mat-form-field>
<mat-label>Span</mat-label>
<input matInput type="number" [formControl]="spanControl" />
<input matInput
type="number"
[formControl]="spanControl" />
@if (spanControl.invalid && (spanControl.dirty || spanControl.touched)) {
@if (spanControl.hasError('required')) {
<small class="error">This field is required.</small>
@ -58,15 +63,18 @@
</mat-card-content>
<mat-card-actions>
@if (isNew) {
<button mat-button (click)="save()">
<button mat-button
(click)="save()">
<mat-icon>add</mat-icon>Add
</button>
} @else {
<button mat-button (click)="save()">
<button mat-button
(click)="save()">
<mat-icon>save</mat-icon>Save
</button>
}
<button mat-button (click)="dialogRef.close()">
<button mat-button
(click)="dialogRef.close()">
<mat-icon>cancel</mat-icon>Cancel
</button>
</mat-card-actions>

View File

@ -10,6 +10,7 @@ import { HermesClientService } from '../../hermes-client.service';
import { GroupDropdownComponent } from '../../groups/group-dropdown/group-dropdown.component';
import { Group } from '../../shared/models/group';
import { Policy } from '../../shared/models/policy';
import { PolicyAddFormComponent } from '../policy-add-form/policy-add-form.component';
@Component({
selector: 'policy-item-edit',
@ -21,6 +22,7 @@ import { Policy } from '../../shared/models/policy';
MatIconModule,
MatInputModule,
ReactiveFormsModule,
PolicyAddFormComponent
],
templateUrl: './policy-item-edit.component.html',
styleUrl: './policy-item-edit.component.scss'

View File

@ -1,40 +1,59 @@
<table mat-table [dataSource]="policies" class="mat-elevation-z8">
<table mat-table
[dataSource]="policies"
class="mat-elevation-z8">
<ng-container matColumnDef="path">
<th mat-header-cell *matHeaderCellDef>Path</th>
<td mat-cell *matCellDef="let policy">
<th mat-header-cell
*matHeaderCellDef>Path</th>
<td mat-cell
*matCellDef="let policy">
{{policy.path}}
</td>
</ng-container>
<ng-container matColumnDef="group">
<th mat-header-cell *matHeaderCellDef>Group</th>
<td mat-cell *matCellDef="let policy">
<th mat-header-cell
*matHeaderCellDef>Group</th>
<td mat-cell
*matCellDef="let policy">
{{getGroupById(policy.group_id)?.name || '\<unknown group\>'}}
</td>
</ng-container>
<ng-container matColumnDef="usage">
<th mat-header-cell *matHeaderCellDef>Usage Rate</th>
<td mat-cell class="center" *matCellDef="let policy">
<th mat-header-cell
*matHeaderCellDef>Usage Rate</th>
<td mat-cell
class="center"
*matCellDef="let policy">
{{policy.usage}}
</td>
</ng-container>
<ng-container matColumnDef="span">
<th mat-header-cell *matHeaderCellDef>Span (ms)</th>
<td mat-cell class="center" *matCellDef="let policy">
<th mat-header-cell
*matHeaderCellDef>Span (ms)</th>
<td mat-cell
class="center"
*matCellDef="let policy">
{{policy.span}}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> Actions </th>
<td mat-cell *matCellDef="let policy">
<button mat-button (click)="edit(policy)"><mat-icon>edit</mat-icon>Edit</button>
<button mat-button class="delete" (click)="delete(policy)"><mat-icon>delete</mat-icon>Delete</button>
<th mat-header-cell
*matHeaderCellDef> Actions </th>
<td mat-cell
*matCellDef="let policy">
<button mat-button
(click)="edit(policy)"><mat-icon>edit</mat-icon>Edit</button>
<button mat-button
class="delete"
(click)="delete(policy)"><mat-icon>delete</mat-icon>Delete</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr mat-header-row
*matHeaderRowDef="displayedColumns"></tr>
<tr mat-row
*matRowDef="let row; columns: displayedColumns;"></tr>
</table>

View File

@ -7,6 +7,6 @@ table {
color: red;
}
button ~ button {
button~button {
margin-left: 1em;
}

View File

@ -1,6 +1,8 @@
<h4>Policies</h4>
<div class="add">
<policy-add-button [policies]="policies" [groups]="groups" (policy)="addPolicy($event)" />
<policy-add-button [policies]="policies"
[groups]="groups"
(policy)="addPolicy($event)" />
</div>
<div>

View File

@ -5,15 +5,13 @@
</mat-card-title>
</mat-card-header>
<mat-card-content>
<twitch-redemption-dropdown
ngDefaultControl
<twitch-redemption-dropdown ngDefaultControl
[formControl]="redemptionFormControl"
[errorMessages]="redemptionErrorMessages"
[twitchRedemptions]="twitchRedemptions"
[(twitchRedemptionId)]="redemption.redemption_id" />
<action-dropdown
ngDefaultControl
<action-dropdown ngDefaultControl
[formControl]="actionFormControl"
[errorMessages]="actionErrorMessages"
[actions]="redeemableActions"
@ -21,7 +19,10 @@
<mat-form-field>
<mat-label>Order</mat-label>
<input matInput type="number" [formControl]="orderFormControl" [value]="redemption.order" />
<input matInput
type="number"
[formControl]="orderFormControl"
[value]="redemption.order" />
@if (orderFormControl.invalid && (orderFormControl.dirty || orderFormControl.touched)) {
@for (error of orderErrorMessageKeys; track $index) {
@if (orderFormControl.hasError(error)) {
@ -31,16 +32,14 @@
}
</mat-form-field>
<div class="buttons">
<button
mat-icon-button
<button mat-icon-button
class="save"
[disabled]="waitForResponse || formGroups.invalid"
(click)="save()">
<mat-icon>save</mat-icon>
</button>
@if (redemption.id) {
<button
mat-icon-button
<button mat-icon-button
class="delete"
[disabled]="waitForResponse"
(click)="delete()">

View File

@ -42,7 +42,7 @@ button,
margin: 0;
}
button ~ button {
button~button {
margin-left: 2em;
}

View File

@ -1,7 +1,10 @@
<div class="content">
<button mat-button class="add" (click)="add()"><mat-icon>add</mat-icon> Add Redemption</button>
<button mat-button
class="add"
(click)="add()"><mat-icon>add</mat-icon> Add Redemption</button>
<mat-expansion-panel class="filters-expander" (opened)="panelOpenState.set(true)"
<mat-expansion-panel class="filters-expander"
(opened)="panelOpenState.set(true)"
(closed)="panelOpenState.set(false)">
<mat-expansion-panel-header>
<mat-panel-title>Filters</mat-panel-title>
@ -11,40 +14,55 @@
</mat-expansion-panel-header>
<div class="filters">
<twitch-redemption-dropdown [(twitchRedemptionId)]="filter_redemption" [search]="true" />
<action-dropdown [(action)]="filter_action_name" [search]="true" />
<twitch-redemption-dropdown [(twitchRedemptionId)]="filter_redemption"
[search]="true" />
<action-dropdown [(action)]="filter_action_name"
[search]="true" />
</div>
</mat-expansion-panel>
<div class="table-container">
<table mat-table [dataSource]="redemptions" class="mat-elevation-z8">
<table mat-table
[dataSource]="redemptions"
class="mat-elevation-z8">
<ng-container matColumnDef="twitch-redemption">
<th mat-header-cell *matHeaderCellDef>Twitch Redemption Name</th>
<td mat-cell *matCellDef="let redemption">{{getTwitchRedemptionNameById(redemption.redemption_id) || 'Unknown
<th mat-header-cell
*matHeaderCellDef>Twitch Redemption Name</th>
<td mat-cell
*matCellDef="let redemption">{{getTwitchRedemptionNameById(redemption.redemption_id) || 'Unknown
Twitch Redemption'}}</td>
</ng-container>
<ng-container matColumnDef="action-name">
<th mat-header-cell *matHeaderCellDef>Action Name</th>
<td mat-cell *matCellDef="let redemption">{{redemption.action_name}}</td>
<th mat-header-cell
*matHeaderCellDef>Action Name</th>
<td mat-cell
*matCellDef="let redemption">{{redemption.action_name}}</td>
</ng-container>
<ng-container matColumnDef="order">
<th mat-header-cell *matHeaderCellDef>Order</th>
<td mat-cell *matCellDef="let redemption">{{redemption.order}}</td>
<th mat-header-cell
*matHeaderCellDef>Order</th>
<td mat-cell
*matCellDef="let redemption">{{redemption.order}}</td>
</ng-container>
<ng-container matColumnDef="misc">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let redemption">
<button mat-icon-button (click)="openDialog(redemption)">
<th mat-header-cell
*matHeaderCellDef></th>
<td mat-cell
*matCellDef="let redemption">
<button mat-icon-button
(click)="openDialog(redemption)">
<mat-icon>edit</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr mat-header-row
*matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row
*matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
</div>

View File

@ -18,7 +18,7 @@ export class RedemptionsComponent implements OnInit {
http = inject(HttpClient);
route = inject(ActivatedRoute);
redemptionService = inject(RedemptionService);
redemptions: Observable<Redemption[]>|undefined;
redemptions: Observable<Redemption[]> | undefined;
ngOnInit(): void {

View File

@ -1,8 +1,16 @@
<mat-form-field>
<mat-label>Twitch Redemption</mat-label>
<input type="text" matInput placeholder="Pick a Twitch redemption" aria-label="twitch redemption"
[formControl]="formControl" [matAutocomplete]="auto" (blur)="blur()" (input)="input()">
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn" (optionSelected)="select($event.option.value)">
<input type="text"
matInput
placeholder="Pick a Twitch redemption"
aria-label="twitch redemption"
[formControl]="formControl"
[matAutocomplete]="auto"
(blur)="blur()"
(input)="input()">
<mat-autocomplete #auto="matAutocomplete"
[displayWith]="displayFn"
(optionSelected)="select($event.option.value)">
@for (redemption of filteredRedemptions; track redemption.id) {
<mat-option [value]="redemption">{{redemption.title}}</mat-option>
}

View File

@ -7,7 +7,7 @@ import { ApiAuthenticationService } from '../services/api/api-authentication.ser
})
export class AuthAdminGuard implements CanActivate {
constructor(private auth: ApiAuthenticationService, private router: Router) {}
constructor(private auth: ApiAuthenticationService, private router: Router) { }
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
return this.auth.isAuthenticated() && this.auth.isAdmin();

View File

@ -9,6 +9,6 @@ export function createTypeValidator(type: string): ValidatorFn {
return null;
const matches = value.constructor.name === type
return matches ? null: { invalidType: 'Invalid choice.' };
return matches ? null : { invalidType: 'Invalid choice.' };
}
}

View File

@ -6,7 +6,10 @@
<div>
<mat-form-field>
<mat-label>Search</mat-label>
<input matInput cdkFocusInitial type="text" formControlName="search" />
<input matInput
cdkFocusInitial
type="text"
formControlName="search" />
@if (forms.get('search')?.invalid && (forms.get('search')?.dirty || forms.get('search')?.touched)) {
<small class="error">Search is required.</small>
}
@ -15,13 +18,16 @@
<div>
<mat-form-field>
<mat-label>Replace</mat-label>
<input matInput formControlName="replace" />
<input matInput
formControlName="replace" />
</mat-form-field>
</div>
<div>
<mat-form-field>
<mat-label>Regex Options</mat-label>
<mat-select multiple [formControl]="flagControl" [compareWith]="compare"
<mat-select multiple
[formControl]="flagControl"
[compareWith]="compare"
(selectionChange)="onSelectionChange($event)">
<mat-select-trigger>
{{optionsSelected[0] || ''}}
@ -43,11 +49,9 @@
</form>
</mat-dialog-content>
<mat-dialog-actions>
<button
mat-button
<button mat-button
(click)="onCancelClick()">Cancel</button>
<button
mat-button
<button mat-button
(click)="onSaveClick()"
[disabled]="!forms.dirty || forms.invalid || waitForResponse">Save</button>
</mat-dialog-actions>

View File

@ -11,11 +11,15 @@
</li>
<li>
<mat-menu #filterMenu>
<button mat-menu-item (click)="openDialog()">Edit</button>
<button mat-menu-item (click)="onDelete.emit(item)">Delete</button>
<button mat-menu-item
(click)="openDialog()">Edit</button>
<button mat-menu-item
(click)="onDelete.emit(item)">Delete</button>
</mat-menu>
<button mat-icon-button class="small-button" [matMenuTriggerFor]="filterMenu">
<button mat-icon-button
class="small-button"
[matMenuTriggerFor]="filterMenu">
<mat-icon>more_vert</mat-icon>
</button>
</li>

View File

@ -21,7 +21,7 @@ ul {
text-overflow: hidden;
}
> button {
>button {
background: #dddddd;
border-radius: 50%;

View File

@ -9,8 +9,7 @@
</li>
@for (filter of filters; track $index) {
<li>
<tts-filter-item
[item]="filter"
<tts-filter-item [item]="filter"
(onDelete)="deleteFilter($event)" />
</li>
}

View File

@ -7,23 +7,23 @@ ul.data {
padding: 0;
overflow: auto;
> li {
>li {
display: block;
list-style-type: none;
padding: 0.75em 1em;
border-bottom: 1px solid #aaaaaa;
}
> li:first-child {
>li:first-child {
padding: 0 1em;
border-bottom: 0 solid #aaaaaa;
}
> li:last-child {
>li:last-child {
border-bottom: 0 solid #aaaaaa;
}
> li:nth-child(2n) {
>li:nth-child(2n) {
background-color: #f5f5f5;
}
}

View File

@ -2,13 +2,15 @@
<article>
<h3>Filters</h3>
<div>
<button mat-button class="add" aria-label="Add a filter" (click)="openDialog()">
<button mat-button
class="add"
aria-label="Add a filter"
(click)="openDialog()">
<mat-icon>add</mat-icon>Add TTS Filter
</button>
</div>
</article>
<div class="grow">
<tts-filter-list
[filters]="items" />
<tts-filter-list [filters]="items" />
</div>
</main>

View File

@ -1,15 +1,23 @@
<!doctype html>
<html lang="en">
<head>
<head>
<meta charset="utf-8">
<title>Tom-to-Speech</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body class="mat-typography">
<meta name="viewport"
content="width=device-width, initial-scale=1">
<link rel="icon"
type="image/x-icon"
href="favicon.ico">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap"
rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
</head>
<body class="mat-typography">
<app-root></app-root>
</body>
</body>
</html>

View File

@ -1,7 +1,14 @@
/* You can add global styles to this file, and also import other style files */
html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
}
.mat-mdc-dialog-surface {
background-color: transparent !important;