Auto-formatted every file to keep everything consistent.
This commit is contained in:
234
angular.json
234
angular.json
@ -1,124 +1,124 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"hermes-web-angular": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"outputPath": "dist/hermes-web-angular",
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/azure-blue.css",
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": [],
|
||||
"server": "src/main.server.ts",
|
||||
"prerender": true,
|
||||
"ssr": {
|
||||
"entry": "server.ts"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "1024kB",
|
||||
"maximumError": "1MB"
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"hermes-web-angular": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"outputPath": "dist/hermes-web-angular",
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/azure-blue.css",
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": [],
|
||||
"server": "src/main.server.ts",
|
||||
"prerender": true,
|
||||
"ssr": {
|
||||
"entry": "server.ts"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "1024kB",
|
||||
"maximumError": "1MB"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kB",
|
||||
"maximumError": "4kB"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"index": {
|
||||
"input": "src/index.prod.html",
|
||||
"output": "index.html"
|
||||
},
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.development.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kB",
|
||||
"maximumError": "4kB"
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "hermes-web-angular:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "hermes-web-angular:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/azure-blue.css",
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"index": {
|
||||
"input": "src/index.prod.html",
|
||||
"output": "index.html"
|
||||
},
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.development.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "hermes-web-angular:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "hermes-web-angular:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/azure-blue.css",
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28210
package-lock.json
generated
28210
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
98
package.json
98
package.json
@ -1,50 +1,50 @@
|
||||
{
|
||||
"name": "hermes-web-angular",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve -c production --host 0.0.0.0 --watch false",
|
||||
"build": "ng build",
|
||||
"watch": "ng serve -c development --host 0.0.0.0",
|
||||
"test": "ng test",
|
||||
"serve:ssr:hermes-web-angular": "node dist/hermes-web-angular/server/server.mjs"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^19.0.5",
|
||||
"@angular/cdk": "^19.0.4",
|
||||
"@angular/common": "^19.0.5",
|
||||
"@angular/compiler": "^19.0.5",
|
||||
"@angular/core": "^19.0.5",
|
||||
"@angular/forms": "^19.0.5",
|
||||
"@angular/material": "^19.0.4",
|
||||
"@angular/platform-browser": "^19.0.5",
|
||||
"@angular/platform-browser-dynamic": "^19.0.5",
|
||||
"@angular/platform-server": "^19.0.5",
|
||||
"@angular/router": "^19.0.5",
|
||||
"@angular/ssr": "^19.0.6",
|
||||
"angular-oauth2-oidc": "^17.0.2",
|
||||
"express": "^4.18.2",
|
||||
"ngx-socket-io": "^4.7.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"rxjs-websockets": "^9.0.0",
|
||||
"tslib": "^2.3.0",
|
||||
"uuidv4": "^6.2.13",
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^19.0.6",
|
||||
"@angular/cli": "^19.0.6",
|
||||
"@angular/compiler-cli": "^19.0.5",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^18.18.0",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"typescript": "~5.6.3"
|
||||
}
|
||||
}
|
||||
"name": "hermes-web-angular",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve -c production --host 0.0.0.0 --watch false",
|
||||
"build": "ng build",
|
||||
"watch": "ng serve -c development --host 0.0.0.0",
|
||||
"test": "ng test",
|
||||
"serve:ssr:hermes-web-angular": "node dist/hermes-web-angular/server/server.mjs"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^19.0.5",
|
||||
"@angular/cdk": "^19.0.4",
|
||||
"@angular/common": "^19.0.5",
|
||||
"@angular/compiler": "^19.0.5",
|
||||
"@angular/core": "^19.0.5",
|
||||
"@angular/forms": "^19.0.5",
|
||||
"@angular/material": "^19.0.4",
|
||||
"@angular/platform-browser": "^19.0.5",
|
||||
"@angular/platform-browser-dynamic": "^19.0.5",
|
||||
"@angular/platform-server": "^19.0.5",
|
||||
"@angular/router": "^19.0.5",
|
||||
"@angular/ssr": "^19.0.6",
|
||||
"angular-oauth2-oidc": "^17.0.2",
|
||||
"express": "^4.18.2",
|
||||
"ngx-socket-io": "^4.7.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"rxjs-websockets": "^9.0.0",
|
||||
"tslib": "^2.3.0",
|
||||
"uuidv4": "^6.2.13",
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^19.0.6",
|
||||
"@angular/cli": "^19.0.6",
|
||||
"@angular/compiler-cli": "^19.0.5",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^18.18.0",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"typescript": "~5.6.3"
|
||||
}
|
||||
}
|
@ -1,15 +1,16 @@
|
||||
<mat-form-field>
|
||||
<mat-label>Redeemable Action</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
placeholder="Pick a Redeemable Action"
|
||||
aria-label="redeemable action"
|
||||
[formControl]="formControl"
|
||||
[matAutocomplete]="auto"
|
||||
(blur)="blur()"
|
||||
(input)="input()">
|
||||
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn" (optionSelected)="select($event.option.value)">
|
||||
<input matInput
|
||||
type="text"
|
||||
placeholder="Pick a Redeemable Action"
|
||||
aria-label="redeemable action"
|
||||
[formControl]="formControl"
|
||||
[matAutocomplete]="auto"
|
||||
(blur)="blur()"
|
||||
(input)="input()">
|
||||
<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>
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ describe('ActionDropdownComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ActionDropdownComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ActionDropdownComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -75,7 +75,7 @@ export class ActionDropdownComponent implements OnInit {
|
||||
} else if (insenstiveActions.length == 1) {
|
||||
newValue = insenstiveActions[0];
|
||||
}
|
||||
|
||||
|
||||
if (newValue && newValue.name != this.formControl.value) {
|
||||
this.formControl.setValue(newValue);
|
||||
this.actionChange.emit(newValue.name);
|
||||
|
@ -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,8 +55,11 @@
|
||||
@if (field.type == 'text') {
|
||||
<mat-form-field>
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<input matInput type="text" placeholder="{{field.placeholder}}" [formControl]="field.control"
|
||||
[(ngModel)]="action.data[field.key]">
|
||||
<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')) {
|
||||
<small class="error">This field is required.</small>
|
||||
@ -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>
|
||||
@ -77,19 +87,19 @@
|
||||
</mat-form-field>
|
||||
}
|
||||
@else if (field.type == 'text-values') {
|
||||
<mat-form-field>
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<mat-select [formControl]="field.control">
|
||||
@for (value of field.values; track $index) {
|
||||
<mat-option [value]="value">{{value}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
@if (field.control.invalid && (field.control.dirty || field.control.touched)) {
|
||||
@if (field.control.hasError('required')) {
|
||||
<small class="error">This field is required.</small>
|
||||
<mat-form-field>
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<mat-select [formControl]="field.control">
|
||||
@for (value of field.values; track $index) {
|
||||
<mat-option [value]="value">{{value}}</mat-option>
|
||||
}
|
||||
}
|
||||
</mat-form-field>
|
||||
</mat-select>
|
||||
@if (field.control.invalid && (field.control.dirty || field.control.touched)) {
|
||||
@if (field.control.hasError('required')) {
|
||||
<small class="error">This field is required.</small>
|
||||
}
|
||||
}
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
</div>
|
||||
@ -98,17 +108,18 @@
|
||||
}
|
||||
</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
|
||||
(click)="dialogRef.close()">Cancel</button>
|
||||
<button
|
||||
mat-raised-button
|
||||
disabled="{{!formsDirty || !formsValidity || waitForResponse}}"
|
||||
(click)="save()">Save</button>
|
||||
<button mat-raised-button
|
||||
(click)="dialogRef.close()">Cancel</button>
|
||||
<button mat-raised-button
|
||||
disabled="{{!formsDirty || !formsValidity || waitForResponse}}"
|
||||
(click)="save()">Save</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</body>
|
@ -25,6 +25,6 @@
|
||||
color: #ba1a1a;
|
||||
}
|
||||
|
||||
.mdc-button ~ .mdc-button {
|
||||
.mdc-button~.mdc-button {
|
||||
margin-left: 1em;
|
||||
}
|
@ -10,7 +10,7 @@ describe('ActionItemEditComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ActionItemEditComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ActionItemEditComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -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>
|
@ -10,7 +10,7 @@ describe('ActionListComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ActionListComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ActionListComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -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> {{filter.name}}
|
||||
</mat-select-trigger>
|
||||
@ -19,13 +20,15 @@
|
||||
<mat-form-field>
|
||||
<mat-label>Search</mat-label>
|
||||
<input matInput
|
||||
type="text"
|
||||
placeholder="Name of action"
|
||||
[formControl]="searchControl"
|
||||
[(ngModel)]="search">
|
||||
type="text"
|
||||
placeholder="Name of action"
|
||||
[formControl]="searchControl"
|
||||
[(ngModel)]="search">
|
||||
<mat-icon matPrefix>search</mat-icon>
|
||||
</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>
|
@ -1,4 +1,5 @@
|
||||
body, h3 {
|
||||
body,
|
||||
h3 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
@ -21,14 +22,14 @@ section {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
@media (max-width:1250px) {
|
||||
@media (max-width:1250px) {
|
||||
display: block;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
article {
|
||||
display: flex;
|
||||
justify-content:space-around;
|
||||
justify-content: space-around;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ describe('ActionsComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ActionsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ActionsComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -1,4 +1,4 @@
|
||||
<main class="main">
|
||||
<navigation class="navigation" />
|
||||
<router-outlet class="content" />
|
||||
<navigation class="navigation" />
|
||||
<router-outlet class="content" />
|
||||
</main>
|
@ -1,4 +1,4 @@
|
||||
.main {
|
||||
display: grid;
|
||||
grid-template-columns: 20em 0px 1fr;
|
||||
display: grid;
|
||||
grid-template-columns: 20em 0px 1fr;
|
||||
}
|
@ -12,9 +12,9 @@ export const appConfig: ApplicationConfig = {
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes),
|
||||
provideHttpClient(
|
||||
withInterceptors([(req: HttpRequest<unknown>, next: HttpHandlerFn) => {
|
||||
return next(req);
|
||||
}])
|
||||
withInterceptors([(req: HttpRequest<unknown>, next: HttpHandlerFn) => {
|
||||
return next(req);
|
||||
}])
|
||||
),
|
||||
provideOAuthClient(),
|
||||
provideClientHydration(), provideAnimationsAsync()
|
||||
|
@ -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>
|
||||
|
@ -10,7 +10,7 @@ describe('ImpersonationComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ImpersonationComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ImpersonationComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -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>
|
||||
|
@ -10,7 +10,7 @@ describe('LoginComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LoginComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -5,29 +5,29 @@ import { Subscription } from 'rxjs';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'login',
|
||||
standalone: true,
|
||||
imports: [MatCardModule, RouterModule],
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: './login.component.scss'
|
||||
selector: 'login',
|
||||
standalone: true,
|
||||
imports: [MatCardModule, RouterModule],
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: './login.component.scss'
|
||||
})
|
||||
export class LoginComponent implements OnInit, OnDestroy {
|
||||
subscription: Subscription | null;
|
||||
subscription: Subscription | null;
|
||||
|
||||
constructor(private router: Router) {
|
||||
this.subscription = null;
|
||||
}
|
||||
constructor(private router: Router) {
|
||||
this.subscription = null;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
}
|
||||
ngOnInit(): void {
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.subscription)
|
||||
this.subscription.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
login() {
|
||||
document.location.replace(environment.API_HOST + '/auth');
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
if (this.subscription)
|
||||
this.subscription.unsubscribe()
|
||||
}
|
||||
|
||||
login() {
|
||||
document.location.replace(environment.API_HOST + '/auth');
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
@ -10,7 +10,7 @@ describe('TtsLoginComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TtsLoginComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TtsLoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
@ -10,7 +10,7 @@ describe('UserCardComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [UserCardComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(UserCardComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -1,17 +1,18 @@
|
||||
<mat-form-field>
|
||||
<mat-label>Group</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
placeholder="Pick a group"
|
||||
aria-label="group"
|
||||
[formControl]="formControl"
|
||||
[matAutocomplete]="auto"
|
||||
[disabled]="!!groupDisabled"
|
||||
[readonly]="!!groupDisabled"
|
||||
(blur)="blur()"
|
||||
(input)="input()">
|
||||
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn" (optionSelected)="select($event.option.value)">
|
||||
<input matInput
|
||||
type="text"
|
||||
placeholder="Pick a group"
|
||||
aria-label="group"
|
||||
[formControl]="formControl"
|
||||
[matAutocomplete]="auto"
|
||||
[disabled]="!!groupDisabled"
|
||||
[readonly]="!!groupDisabled"
|
||||
(blur)="blur()"
|
||||
(input)="input()">
|
||||
<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>
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ describe('GroupDropdownComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GroupDropdownComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GroupDropdownComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -35,7 +35,7 @@ export class GroupDropdownComponent implements OnInit {
|
||||
this.route.data.subscribe(data => {
|
||||
if (!data['groups'])
|
||||
return;
|
||||
|
||||
|
||||
this.groups = data['groups'];
|
||||
});
|
||||
|
||||
@ -83,13 +83,13 @@ export class GroupDropdownComponent implements OnInit {
|
||||
} else if (insenstiveGroups.length == 1) {
|
||||
newValue = insenstiveGroups[0];
|
||||
}
|
||||
|
||||
|
||||
if (newValue) {
|
||||
this.formControl.setValue(newValue);
|
||||
//this.groupChange.emit(newValue.name);
|
||||
} else if (!newValue)
|
||||
this.formControl.setValue(undefined);
|
||||
//this.groupChange.emit(undefined);
|
||||
//this.groupChange.emit(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,16 +39,14 @@
|
||||
</mat-form-field>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button
|
||||
mat-button
|
||||
[disabled]="waitForResponse || formGroup.invalid"
|
||||
(click)="add()">
|
||||
<button mat-button
|
||||
[disabled]="waitForResponse || formGroup.invalid"
|
||||
(click)="add()">
|
||||
<mat-icon>add</mat-icon>Add
|
||||
</button>
|
||||
<button
|
||||
mat-button
|
||||
[disabled]="waitForResponse"
|
||||
(click)="cancel()">
|
||||
<button mat-button
|
||||
[disabled]="waitForResponse"
|
||||
(click)="cancel()">
|
||||
<mat-icon>cancel</mat-icon>Cancel
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
|
@ -10,7 +10,7 @@ describe('GroupItemEditComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GroupItemEditComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GroupItemEditComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -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>
|
||||
|
@ -10,7 +10,7 @@ describe('GroupItemComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GroupItemComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GroupItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -25,7 +25,7 @@ export class GroupItemComponent implements OnInit {
|
||||
item = input.required<{ group: Group, chatters: GroupChatter[], policies: Policy[] }>();
|
||||
link: string = '';
|
||||
|
||||
|
||||
|
||||
special: boolean = true;
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
<ul>
|
||||
@for (group of groups; track $index) {
|
||||
@for (group of groups; track $index) {
|
||||
<li>
|
||||
<group-item [item]="group" />
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</ul>
|
@ -10,7 +10,7 @@ describe('GroupListComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GroupListComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GroupListComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -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>
|
||||
|
@ -1,4 +1,4 @@
|
||||
.mat-expansion-panel ~ .mat-expansion-panel {
|
||||
.mat-expansion-panel~.mat-expansion-panel {
|
||||
margin-top: 4em;
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ describe('GroupPageComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GroupPageComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GroupPageComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -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"
|
||||
[groups]="items" />
|
||||
<group-list class="groups"
|
||||
[groups]="items" />
|
@ -10,7 +10,7 @@ describe('GroupsComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GroupsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GroupsComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -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)
|
||||
|
@ -3,45 +3,52 @@
|
||||
<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">
|
||||
Groups
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a routerLink="/groups"
|
||||
routerLinkActive="active">
|
||||
Groups
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
|
@ -5,33 +5,33 @@ $secondary_background_color: #DDDDDD;
|
||||
$secondary_font_color: #333333;
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
list-style: none;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
padding: 1em;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
font-size: large;
|
||||
text-decoration: none;
|
||||
color: $primary_font_color;
|
||||
border-radius: 10px;
|
||||
background-color: transparent;
|
||||
padding: 1em;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
font-size: large;
|
||||
text-decoration: none;
|
||||
color: $primary_font_color;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background-color: #FAFAFA;
|
||||
background-color: #FAFAFA;
|
||||
}
|
||||
|
||||
a.active {
|
||||
background-color: #F5F5F5;
|
||||
background-color: #F5F5F5;
|
||||
}
|
@ -10,7 +10,7 @@ describe('NavigationComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NavigationComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(NavigationComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -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 { }
|
@ -1,4 +1,5 @@
|
||||
<button mat-button (click)="openDialog()">
|
||||
<button mat-button
|
||||
(click)="openDialog()">
|
||||
<mat-icon>add</mat-icon>
|
||||
Add a policy
|
||||
</button>
|
@ -10,7 +10,7 @@ describe('PolicyAddButtonComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PolicyAddButtonComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PolicyAddButtonComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -19,9 +19,9 @@ 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>();
|
||||
|
||||
|
||||
|
||||
openDialog(): void {
|
||||
const dialogRef = this.dialog.open(PolicyItemEditComponent, {
|
||||
|
@ -1,25 +1,17 @@
|
||||
<div>
|
||||
<form
|
||||
standalone>
|
||||
<mat-form-field>
|
||||
<input
|
||||
name="path"
|
||||
type="text"
|
||||
placeholder="Pick one"
|
||||
[(ngModel)]="newPolicyName"
|
||||
matInput
|
||||
[formControl]="myControl"
|
||||
[matAutocomplete]="auto" />
|
||||
<mat-autocomplete #auto="matAutocomplete">
|
||||
@for (option of filteredPolicies | async; track option) {
|
||||
<mat-option [value]="option">{{option}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
<button
|
||||
mat-flat-button
|
||||
(click)="addNewPolicy()">
|
||||
Add
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<form standalone>
|
||||
<mat-form-field>
|
||||
<mat-label>Path</mat-label>
|
||||
<input name="path"
|
||||
type="text"
|
||||
placeholder="Pick one"
|
||||
[(ngModel)]="policy"
|
||||
matInput
|
||||
[formControl]="policyControl"
|
||||
[matAutocomplete]="auto" />
|
||||
<mat-autocomplete #auto="matAutocomplete">
|
||||
@for (option of filteredPolicies | async; track option) {
|
||||
<mat-option [value]="option">{{option}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</form>
|
@ -10,7 +10,7 @@ describe('PolicyAddFormComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PolicyAddFormComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PolicyAddFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -4,71 +4,68 @@ 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" },
|
||||
{ path: "tts.chat", description: "Anything to do with chat" },
|
||||
{ path: "tts.chat.bits.read", description: "To read chat messages with bits via TTS" },
|
||||
{ path: "tts.chat.messages.read", description: "To read chat messages via TTS" },
|
||||
{ path: "tts.chat.redemptions.read", description: "To read channel point redemption messages via TTS" },
|
||||
{ path: "tts.chat.subscriptions.read", description: "To read chat messages from subscriptions via TTS" },
|
||||
{ path: "tts.commands", description: "To execute commands for TTS" },
|
||||
{ path: "tts.commands.nightbot", description: "To use !nightbot command" },
|
||||
{ path: "tts.commands.obs", description: "To use !obs command" },
|
||||
{ path: "tts.commands.refresh", description: "To use !refresh command" },
|
||||
{ path: "tts.commands.skip", description: "To use !skip command" },
|
||||
{ path: "tts.commands.skipall", description: "To use !skipall command" },
|
||||
{ path: "tts.commands.tts", description: "To use !tts command" },
|
||||
{ path: "tts.commands.tts.join", description: "To use !tts join command" },
|
||||
{ path: "tts.commands.tts.leave", description: "To use !tts leave command" },
|
||||
{ path: "tts.commands.version", description: "To use !version command" },
|
||||
{ path: "tts.commands.voice", description: "To use !voice command" },
|
||||
{ path: "tts.commands.voice.admin", description: "To use !voice command on others" },
|
||||
{ path: "tts", description: "Anything to do with TTS" },
|
||||
{ path: "tts.chat", description: "Anything to do with chat" },
|
||||
{ path: "tts.chat.bits.read", description: "To read chat messages with bits via TTS" },
|
||||
{ path: "tts.chat.messages.read", description: "To read chat messages via TTS" },
|
||||
{ path: "tts.chat.redemptions.read", description: "To read channel point redemption messages via TTS" },
|
||||
{ path: "tts.chat.subscriptions.read", description: "To read chat messages from subscriptions via TTS" },
|
||||
{ path: "tts.commands", description: "To execute commands for TTS" },
|
||||
{ path: "tts.commands.nightbot", description: "To use !nightbot command" },
|
||||
{ path: "tts.commands.obs", description: "To use !obs command" },
|
||||
{ path: "tts.commands.refresh", description: "To use !refresh command" },
|
||||
{ path: "tts.commands.skip", description: "To use !skip command" },
|
||||
{ path: "tts.commands.skipall", description: "To use !skipall command" },
|
||||
{ path: "tts.commands.tts", description: "To use !tts command" },
|
||||
{ path: "tts.commands.tts.join", description: "To use !tts join command" },
|
||||
{ path: "tts.commands.tts.leave", description: "To use !tts leave command" },
|
||||
{ path: "tts.commands.version", description: "To use !version command" },
|
||||
{ path: "tts.commands.voice", description: "To use !voice command" },
|
||||
{ path: "tts.commands.voice.admin", description: "To use !voice command on others" },
|
||||
]
|
||||
|
||||
@Component({
|
||||
selector: 'policy-add-form',
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
FormsModule,
|
||||
MatAutocompleteModule,
|
||||
MatButtonModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
templateUrl: './policy-add-form.component.html',
|
||||
styleUrl: './policy-add-form.component.scss'
|
||||
selector: 'policy-add-form',
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
FormsModule,
|
||||
MatAutocompleteModule,
|
||||
MatButtonModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
templateUrl: './policy-add-form.component.html',
|
||||
styleUrl: './policy-add-form.component.scss'
|
||||
})
|
||||
export class PolicyAddFormComponent {
|
||||
myControl = new FormControl('');
|
||||
newPolicyName: string = '';
|
||||
filteredPolicies: Observable<string[]>;
|
||||
policyControl = new FormControl('');
|
||||
policy: string = '';
|
||||
filteredPolicies: Observable<string[]>;
|
||||
|
||||
constructor(private events: EventService, private hermes: HermesClientService) {
|
||||
this.filteredPolicies = this.myControl.valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => this._filter(value || '')),
|
||||
);
|
||||
constructor() {
|
||||
this.filteredPolicies = this.policyControl.valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => this._filter(value || '')),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.filteredPolicies = this.policyControl.valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => this._filter(value || '')),
|
||||
);
|
||||
}
|
||||
|
||||
private _filter(value: string): string[] {
|
||||
const filterValue = value.toLowerCase();
|
||||
const names = Policies.map(p => p.path);
|
||||
if (names.includes(filterValue)) {
|
||||
return names;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.filteredPolicies = this.myControl.valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => this._filter(value || '')),
|
||||
);
|
||||
}
|
||||
|
||||
private _filter(value: string): string[] {
|
||||
const filterValue = value.toLowerCase();
|
||||
|
||||
return Policies.map(p => p.path).filter(option => option.toLowerCase().includes(filterValue));
|
||||
}
|
||||
|
||||
addNewPolicy() {
|
||||
this.events.emit('addPolicy', this.newPolicyName);
|
||||
this.newPolicyName = "";
|
||||
}
|
||||
return names.filter(option => option.toLowerCase().includes(filterValue));
|
||||
}
|
||||
}
|
||||
|
@ -3,16 +3,17 @@
|
||||
<mat-card-title>{{isNew ? 'Add' : 'Edit'}} Policy</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<group-dropdown
|
||||
ngDefaultControl
|
||||
[formControl]="groupControl"
|
||||
[groups]="data.groups"
|
||||
[group]="data.group_id"
|
||||
[groupDisabled]="data.groupDisabled"
|
||||
[errorMessages]="groupErrorMessages" />
|
||||
<group-dropdown ngDefaultControl
|
||||
[formControl]="groupControl"
|
||||
[groups]="data.groups"
|
||||
[group]="data.group_id"
|
||||
[groupDisabled]="data.groupDisabled"
|
||||
[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>
|
||||
|
@ -10,7 +10,7 @@ describe('PolicyItemEditComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PolicyItemEditComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PolicyItemEditComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -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'
|
||||
|
@ -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>
|
@ -7,6 +7,6 @@ table {
|
||||
color: red;
|
||||
}
|
||||
|
||||
button ~ button {
|
||||
button~button {
|
||||
margin-left: 1em;
|
||||
}
|
@ -10,7 +10,7 @@ describe('PolicyTableComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PolicyTableComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PolicyTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -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>
|
||||
|
@ -1,5 +1,5 @@
|
||||
h4 {
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.add {
|
||||
|
@ -10,7 +10,7 @@ describe('PolicyComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PolicyComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PolicyComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -5,23 +5,24 @@
|
||||
</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<twitch-redemption-dropdown
|
||||
ngDefaultControl
|
||||
[formControl]="redemptionFormControl"
|
||||
[errorMessages]="redemptionErrorMessages"
|
||||
[twitchRedemptions]="twitchRedemptions"
|
||||
[(twitchRedemptionId)]="redemption.redemption_id" />
|
||||
<twitch-redemption-dropdown ngDefaultControl
|
||||
[formControl]="redemptionFormControl"
|
||||
[errorMessages]="redemptionErrorMessages"
|
||||
[twitchRedemptions]="twitchRedemptions"
|
||||
[(twitchRedemptionId)]="redemption.redemption_id" />
|
||||
|
||||
<action-dropdown ngDefaultControl
|
||||
[formControl]="actionFormControl"
|
||||
[errorMessages]="actionErrorMessages"
|
||||
[actions]="redeemableActions"
|
||||
[(action)]="redemption.action_name" />
|
||||
|
||||
<action-dropdown
|
||||
ngDefaultControl
|
||||
[formControl]="actionFormControl"
|
||||
[errorMessages]="actionErrorMessages"
|
||||
[actions]="redeemableActions"
|
||||
[(action)]="redemption.action_name" />
|
||||
|
||||
<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,19 +32,17 @@
|
||||
}
|
||||
</mat-form-field>
|
||||
<div class="buttons">
|
||||
<button
|
||||
mat-icon-button
|
||||
class="save"
|
||||
[disabled]="waitForResponse || formGroups.invalid"
|
||||
(click)="save()">
|
||||
<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
|
||||
class="delete"
|
||||
[disabled]="waitForResponse"
|
||||
(click)="delete()">
|
||||
<button mat-icon-button
|
||||
class="delete"
|
||||
[disabled]="waitForResponse"
|
||||
(click)="delete()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ button,
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button ~ button {
|
||||
button~button {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ describe('RedemptionItemEditComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [RedemptionItemEditComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(RedemptionItemEditComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -1,8 +1,11 @@
|
||||
<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)"
|
||||
(closed)="panelOpenState.set(false)">
|
||||
<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>
|
||||
<mat-panel-description>
|
||||
@ -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>
|
@ -10,7 +10,7 @@ describe('RedemptionListComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [RedemptionListComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(RedemptionListComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -10,7 +10,7 @@ describe('RedemptionsComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [RedemptionsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(RedemptionsComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -18,9 +18,9 @@ export class RedemptionsComponent implements OnInit {
|
||||
http = inject(HttpClient);
|
||||
route = inject(ActivatedRoute);
|
||||
redemptionService = inject(RedemptionService);
|
||||
redemptions: Observable<Redemption[]>|undefined;
|
||||
redemptions: Observable<Redemption[]> | undefined;
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ describe('TwitchRedemptionDropdownComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TwitchRedemptionDropdownComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TwitchRedemptionDropdownComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -3,11 +3,11 @@ import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from
|
||||
import { ApiAuthenticationService } from '../services/api/api-authentication.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root'
|
||||
})
|
||||
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();
|
||||
|
@ -2,20 +2,20 @@ import { Injectable } from "@angular/core";
|
||||
import { Subject } from "rxjs"
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root'
|
||||
})
|
||||
export default class EventService {
|
||||
private subject = new Subject();
|
||||
private subject = new Subject();
|
||||
|
||||
emit(eventName: string, payload: any) {
|
||||
this.subject.next({ eventName, payload })
|
||||
}
|
||||
emit(eventName: string, payload: any) {
|
||||
this.subject.next({ eventName, payload })
|
||||
}
|
||||
|
||||
listen(eventName: string, callback: (event: any) => void) {
|
||||
return this.subject.asObservable().subscribe((next: any) => {
|
||||
if (eventName == next.eventName) {
|
||||
callback(next.payload)
|
||||
}
|
||||
})
|
||||
}
|
||||
listen(eventName: string, callback: (event: any) => void) {
|
||||
return this.subject.asObservable().subscribe((next: any) => {
|
||||
if (eventName == next.eventName) {
|
||||
callback(next.payload)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms";
|
||||
export function createItemExistsInArrayValidator(items: any[], getter: (value: any) => any): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
const value = control.value;
|
||||
|
||||
|
||||
if (!value)
|
||||
return null;
|
||||
|
||||
|
@ -4,11 +4,11 @@ import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms";
|
||||
export function createTypeValidator(type: string): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
const value = control.value;
|
||||
|
||||
|
||||
if (!value)
|
||||
return null;
|
||||
|
||||
const matches = value.constructor.name === type
|
||||
return matches ? null: { invalidType: 'Invalid choice.' };
|
||||
return matches ? null : { invalidType: 'Invalid choice.' };
|
||||
}
|
||||
}
|
@ -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,14 +18,17 @@
|
||||
<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"
|
||||
(selectionChange)="onSelectionChange($event)">
|
||||
<mat-select multiple
|
||||
[formControl]="flagControl"
|
||||
[compareWith]="compare"
|
||||
(selectionChange)="onSelectionChange($event)">
|
||||
<mat-select-trigger>
|
||||
{{optionsSelected[0] || ''}}
|
||||
@if ((flagControl.value?.length || 0) > 1) {
|
||||
@ -43,13 +49,11 @@
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button
|
||||
mat-button
|
||||
(click)="onCancelClick()">Cancel</button>
|
||||
<button
|
||||
mat-button
|
||||
(click)="onSaveClick()"
|
||||
[disabled]="!forms.dirty || forms.invalid || waitForResponse">Save</button>
|
||||
<button mat-button
|
||||
(click)="onCancelClick()">Cancel</button>
|
||||
<button mat-button
|
||||
(click)="onSaveClick()"
|
||||
[disabled]="!forms.dirty || forms.invalid || waitForResponse">Save</button>
|
||||
</mat-dialog-actions>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
@ -10,7 +10,7 @@ describe('FilterItemEditComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [FilterItemEditComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(FilterItemEditComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -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>
|
||||
|
@ -1,40 +1,40 @@
|
||||
input {
|
||||
display: inline;
|
||||
font-size: large;
|
||||
row-gap: 2em;
|
||||
display: inline;
|
||||
font-size: large;
|
||||
row-gap: 2em;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
white-space: pre;
|
||||
text-align: start;
|
||||
text-wrap: wrap;
|
||||
li {
|
||||
list-style-type: none;
|
||||
white-space: pre;
|
||||
text-align: start;
|
||||
text-wrap: wrap;
|
||||
|
||||
span {
|
||||
overflow: hidden;
|
||||
text-overflow: hidden;
|
||||
}
|
||||
|
||||
> button {
|
||||
background: #dddddd;
|
||||
border-radius: 50%;
|
||||
|
||||
:hover {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
span {
|
||||
overflow: hidden;
|
||||
text-overflow: hidden;
|
||||
}
|
||||
|
||||
li:nth-child(1),
|
||||
li:nth-child(2) {
|
||||
flex: 1;
|
||||
>button {
|
||||
background: #dddddd;
|
||||
border-radius: 50%;
|
||||
|
||||
:hover {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
li:nth-child(1),
|
||||
li:nth-child(2) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.small-button {
|
||||
|
@ -11,7 +11,7 @@ describe('FilterItemComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [FilterItemComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(FilterItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -1,17 +1,16 @@
|
||||
<div>
|
||||
<ul class="data">
|
||||
<li>
|
||||
<ul class="header">
|
||||
<li>Search</li>
|
||||
<li>Replace</li>
|
||||
<li></li>
|
||||
</ul>
|
||||
<ul class="header">
|
||||
<li>Search</li>
|
||||
<li>Replace</li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</li>
|
||||
@for (filter of filters; track $index) {
|
||||
<li>
|
||||
<tts-filter-item
|
||||
[item]="filter"
|
||||
(onDelete)="deleteFilter($event)" />
|
||||
<tts-filter-item [item]="filter"
|
||||
(onDelete)="deleteFilter($event)" />
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ describe('FilterListComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [FilterListComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(FilterListComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -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>
|
@ -10,7 +10,7 @@ describe('FiltersComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [FiltersComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(FiltersComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -10,7 +10,7 @@ describe('TwitchAuthCallbackComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TwitchAuthCallbackComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TwitchAuthCallbackComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
@ -1,15 +1,23 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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">
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<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">
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -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;
|
||||
@ -14,17 +21,17 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
|
||||
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
box-shadow: inset 0 0 5px grey;
|
||||
box-shadow: inset 0 0 5px grey;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: darkgrey;
|
||||
background: darkgrey;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Handle on hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgb(122, 122, 122);
|
||||
background: rgb(122, 122, 122);
|
||||
}
|
@ -1,19 +1,19 @@
|
||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": [
|
||||
"node"
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts",
|
||||
"src/main.server.ts",
|
||||
"server.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts",
|
||||
"src/main.server.ts",
|
||||
"server.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,33 +1,33 @@
|
||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/out-tsc",
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "bundler",
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"useDefineForClassFields": false,
|
||||
"lib": [
|
||||
"ES2022",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/out-tsc",
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "bundler",
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"useDefineForClassFields": false,
|
||||
"lib": [
|
||||
"ES2022",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user