Merge pull request 'SES-156 create character from template' (#86) from SES-156 into dev

Reviewed-on: #86
This commit is contained in:
Łukasz Góreczny 2021-01-23 21:34:41 +01:00
commit 1ee65ef764
25 changed files with 652 additions and 85 deletions

View File

@ -5,6 +5,8 @@ import { RegistrationComponent } from './app/components/registration/registratio
import {GameMasterDashboardComponent} from './app/components/game-master-dashboard/game-master-dashboard.component'; import {GameMasterDashboardComponent} from './app/components/game-master-dashboard/game-master-dashboard.component';
import {PlayerDashboardComponent} from './app/components/player-dashboard/player-dashboard.component'; import {PlayerDashboardComponent} from './app/components/player-dashboard/player-dashboard.component';
import { SelectCharacterComponent } from './app/components/select-character/select-character.component'; import { SelectCharacterComponent } from './app/components/select-character/select-character.component';
import {CreateCharacterComponent} from "./app/components/create-character/create-character.component";
import {PersonalizeTemplateComponent} from "./app/components/personalize-template/personalize-template.component";
const routes: Routes = [ const routes: Routes = [
{ {
@ -37,6 +39,16 @@ const routes: Routes = [
component: SelectCharacterComponent, component: SelectCharacterComponent,
pathMatch: 'full' pathMatch: 'full'
}, },
{
path: 'create-character-step-1',
component: CreateCharacterComponent,
pathMatch: 'full'
},
{
path: 'create-character-step-2',
component: PersonalizeTemplateComponent,
pathMatch: 'full'
}
]; ];
export const appRoutingModule = RouterModule.forRoot(routes); export const appRoutingModule = RouterModule.forRoot(routes);

View File

@ -62,6 +62,8 @@ import { MessageDialogComponent } from './shared/message-dialog/message-dialog.c
import { GameMasterTurntrackerComponent } from './components/game-master-turntracker/game-master-turntracker.component'; import { GameMasterTurntrackerComponent } from './components/game-master-turntracker/game-master-turntracker.component';
import { DragDropModule } from '@angular/cdk/drag-drop'; import { DragDropModule } from '@angular/cdk/drag-drop';
import { ChooseMonsterDialogComponent } from './components/choose-monster-dialog/choose-monster-dialog.component'; import { ChooseMonsterDialogComponent } from './components/choose-monster-dialog/choose-monster-dialog.component';
import { CreateCharacterComponent } from './components/create-character/create-character.component';
import { PersonalizeTemplateComponent } from './components/personalize-template/personalize-template.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -90,6 +92,8 @@ import { ChooseMonsterDialogComponent } from './components/choose-monster-dialog
MessageDialogComponent, MessageDialogComponent,
GameMasterTurntrackerComponent, GameMasterTurntrackerComponent,
ChooseMonsterDialogComponent, ChooseMonsterDialogComponent,
CreateCharacterComponent,
PersonalizeTemplateComponent,
], ],
imports: [ imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
@ -151,6 +155,8 @@ import { ChooseMonsterDialogComponent } from './components/choose-monster-dialog
MessageDialogComponent, MessageDialogComponent,
GameMasterTurntrackerComponent, GameMasterTurntrackerComponent,
ChooseMonsterDialogComponent, ChooseMonsterDialogComponent,
CreateCharacterComponent,
PersonalizeTemplateComponent,
], ],
}) })
export class AppModule {} export class AppModule {}

View File

@ -0,0 +1,85 @@
@import '../../../styles.css';
mat-form-field {
color: #df7c0f;
}
:host {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
input {
min-width: 150px;
max-width: 500px;
width: 100%;
}
.container {
margin: 5%;
display: flex;
flex-direction: column;
}
.container h1 {
margin-bottom: 1rem;
}
.form-container {
padding: 10px;
margin: 5px;
}
.primary-text {
height: 100px;
color: #df7c0f;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1%;
margin: 1%;
}
.header {
font-size: 20px;
}
.mat-divider--custom-style {
background-color: #9e8b6e;
box-shadow: 0 1px 0 0 #d8d8d8;
}
@media (max-width: 400px) {
.container {
margin-left: 0%;
width: 300px;
padding-top: 5%;
}
}
@media (max-width: 400px) {
.container {
margin-left: 0%;
width: 300px;
padding-top: 5%;
}
}
@media (min-width: 401px) {
.container {
margin-left: 1%;
width: 450px;
padding-top: 10%;
}
}
@media (min-width: 768px) {
.container {
width: 550px;
padding-top: 5%;
}
}

View File

@ -0,0 +1,15 @@
<div class="container">
<mat-icon matSuffix class="arrow-back arrow-select" (click)="onArrowBackClick()">arrow_back</mat-icon>
<div class="primary-text header">Create character</div>
<div class="primary-text header">Step 1 - Choose one of template</div>
<mat-divider class="mat-divider--custom-style"></mat-divider>
<mat-list>
<mat-list-item *ngFor="let character of charactersTemplateList">
<mat-icon mat-list-icon>account_circle</mat-icon>
<div mat-line (click)="onCharacterClick(character.id)">{{character.name}}</div>
<mat-icon matSuffix class="arrow-forward arrow-select" (click)="onCharacterClick(character.id)">arrow_forward</mat-icon>
<div mat-line> {{character.class}}, {{character.race}}</div>
<mat-divider class="mat-divider--custom-style"></mat-divider>
</mat-list-item>
</mat-list>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CreateCharacterComponent } from './create-character.component';
describe('CreateCharacterComponent', () => {
let component: CreateCharacterComponent;
let fixture: ComponentFixture<CreateCharacterComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CreateCharacterComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CreateCharacterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,66 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { AppState } from '../../store/models/app-state.model';
import { first } from 'rxjs/operators';
import { ErrorResponse } from '../../../types/ErrorResponse';
import { HttpErrorResponse } from '@angular/common/http';
import { CharacterFromTemplatesViewModel } from '../../../types/viewmodels/character-viewmodels/CharacterFromTemplatesViewModel';
import { CharacterService } from '../../../services/character.service';
import {
ChooseTemplateToCreateCharacter,
ClearCharacterId,
} from '../../store/actions/player.action';
@Component({
selector: 'app-create-character',
templateUrl: './create-character.component.html',
styleUrls: ['./create-character.component.css'],
})
export class CreateCharacterComponent implements OnInit {
charactersTemplateList: CharacterFromTemplatesViewModel[];
constructor(
private router: Router,
private store: Store<AppState>,
private characterService: CharacterService
) {}
ngOnInit() {
this.getTemplateCharacters();
}
onArrowBackClick() {
this.store
.select((s) => s.playerStore.characterId)
.pipe(first())
.subscribe((characterId) => {
if (characterId !== null) {
this.store.dispatch(new ClearCharacterId());
}
this.router.navigate(['select-character']);
});
}
onCharacterClick(characterId: number) {
this.store.dispatch(new ChooseTemplateToCreateCharacter({ characterId }));
this.router.navigate(['create-character-step-2']);
}
getTemplateCharacters() {
this.characterService
.getTemplateCharacters()
.pipe(first())
.subscribe(
(charactersTemplateList) => {
this.charactersTemplateList = charactersTemplateList;
},
(error: ErrorResponse | HttpErrorResponse) => {
if (error instanceof HttpErrorResponse) {
error = error.error as ErrorResponse;
}
console.error(error.message);
}
);
}
}

View File

@ -0,0 +1,77 @@
@import '../../../styles.css';
mat-form-field {
color: #df7c0f;
}
:host {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
input {
min-width: 150px;
max-width: 500px;
width: 100%;
}
.container {
margin: 5%;
display: flex;
flex-direction: column;
}
.container h1 {
margin-bottom: 1rem;
}
.form-container {
padding: 10px;
margin: 5px;
}
.primary-text {
height: 100px;
color: #df7c0f;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1%;
margin: 1%;
}
.header {
font-size: 20px;
}
.mat-divider--custom-style {
background-color: #9e8b6e;
box-shadow: 0 1px 0 0 #d8d8d8;
}
@media (max-width: 400px) {
.container {
margin-left: 0%;
width: 300px;
padding-top: 5%;
}
}
@media (min-width: 401px) {
.container {
margin-left: 1%;
width: 450px;
padding-top: 10%;
}
}
@media (min-width: 768px) {
.container {
width: 550px;
padding-top: 5%;
}
}

View File

@ -0,0 +1,28 @@
<div [formGroup]="personalizeTemplateFormGroup" class="container">
<mat-icon matSuffix class="arrow-back arrow-select" (click)="onArrowBackClick()">arrow_back</mat-icon>
<div class="primary-text header">Create character</div>
<div class="primary-text header">Step 2 - Personalize template</div>
<mat-form-field class="form-container">
<input
id="newName"
matInput
formControlName="newName"
placeholder="New character name"
type="text"
name="newName">
<mat-error *ngIf="personalizeTemplateFormGroup?.get('newName').hasError('required')">
If the field is left blank, your character will have the default name
</mat-error>
<mat-icon matSuffix>person</mat-icon>
</mat-form-field>
<mat-error *ngIf="apiError">
{{apiErrorMessage}}
</mat-error>
<button
id="create-character-submit-button"
mat-raised-button
class="btn-primary form-container"
(click)="onCreateCharacterButton()">
CONTINUE
</button>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PersonalizeTemplateComponent } from './personalize-template.component';
describe('PersonalizeTemplateComponent', () => {
let component: PersonalizeTemplateComponent;
let fixture: ComponentFixture<PersonalizeTemplateComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PersonalizeTemplateComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PersonalizeTemplateComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,71 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Subscription } from 'rxjs';
import { CharacterService } from '../../../services/character.service';
import { ErrorResponse } from '../../../types/ErrorResponse';
import { HttpErrorResponse } from '@angular/common/http';
import { AppState } from '../../store/models/app-state.model';
import { Store } from '@ngrx/store';
import { first } from 'rxjs/operators';
@Component({
selector: 'app-personalize-template',
templateUrl: './personalize-template.component.html',
styleUrls: ['./personalize-template.component.css'],
})
export class PersonalizeTemplateComponent implements OnInit {
allSubscriptions = new Subscription();
apiError = false;
apiErrorMessage = '';
constructor(
private router: Router,
private formBuilder: FormBuilder,
private characterService: CharacterService,
private store: Store<AppState>
) {}
public personalizeTemplateFormGroup: FormGroup = this.formBuilder.group({
newName: ['', [Validators.required]],
});
ngOnInit() {}
onCreateCharacterButton() {
this.store
.select((s) => {
return {
userId: s.appStore.userId,
characterId: s.playerStore.characterId,
};
})
.pipe(first())
.subscribe((result) => {
this.characterService
.createCharacterFromTemplate(
result.characterId,
result.userId,
this.personalizeTemplateFormGroup.value['newName']
)
.pipe(first())
.subscribe(
() => {
this.router.navigate(['select-character']);
},
(error: ErrorResponse | HttpErrorResponse) => {
console.error(error);
if (error instanceof HttpErrorResponse) {
error = error.error as ErrorResponse;
}
this.apiError = true;
this.apiErrorMessage = error.message;
}
);
});
}
onArrowBackClick() {
this.router.navigate(['create-character-step-1']);
}
}

View File

@ -13,22 +13,20 @@ import { AppState } from '../../store/models/app-state.model';
@Component({ @Component({
selector: 'app-player-weapons-table', selector: 'app-player-weapons-table',
templateUrl: './player-weapons-table.component.html', templateUrl: './player-weapons-table.component.html',
styleUrls: ['./player-weapons-table.component.css'] styleUrls: ['./player-weapons-table.component.css'],
}) })
export class PlayerWeaponsTableComponent implements OnInit { export class PlayerWeaponsTableComponent implements OnInit {
weapons: WeaponViewModel[]; weapons: WeaponViewModel[];
displayedColumns: string[] = [ displayedColumns: string[] = ['name', 'weaponType', 'weight', 'cost'];
'name',
'weaponType',
'weight',
'cost',
];
dataSource: MatTableDataSource<WeaponViewModel>; dataSource: MatTableDataSource<WeaponViewModel>;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
@ViewChild(MatSort, { static: true }) sort: MatSort; @ViewChild(MatSort, { static: true }) sort: MatSort;
constructor(private store: Store<AppState>, private equipmentService: EquipmentService) {} constructor(
private store: Store<AppState>,
private equipmentService: EquipmentService
) {}
ngOnInit() { ngOnInit() {
this.getCharacterWeapons(); this.getCharacterWeapons();
@ -40,7 +38,7 @@ export class PlayerWeaponsTableComponent implements OnInit {
.pipe(first()) .pipe(first())
.subscribe((characterId) => { .subscribe((characterId) => {
this.equipmentService this.equipmentService
.getCharacterWeapons(1) .getCharacterWeapons(characterId)
.pipe(first()) .pipe(first())
.subscribe( .subscribe(
(result) => { (result) => {

View File

@ -64,6 +64,17 @@ input {
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.container {
width: 550px;
padding-top: 0;
}
.form-container {
padding: 10px;
margin: 0px;
}
}
@media (min-width: 1800px) {
.container { .container {
width: 550px; width: 550px;
padding-top: 5%; padding-top: 5%;

View File

@ -38,7 +38,8 @@
required required
placeholder="Confirm Password" placeholder="Confirm Password"
type="password" type="password"
name="confirmPassword"/> name="confirmPassword"
(keyup.enter)="Register()"/>
<mat-icon matSuffix >lock</mat-icon> <mat-icon matSuffix >lock</mat-icon>
<mat-error *ngIf="signUpFormGroup?.get('newAccount')['controls']?.confirmPassword?.hasError('required')"> <mat-error *ngIf="signUpFormGroup?.get('newAccount')['controls']?.confirmPassword?.hasError('required')">
Confirm your password Confirm your password

View File

@ -69,3 +69,8 @@ input {
padding-top: 5%; padding-top: 5%;
} }
} }
.mat-divider--custom-style {
background-color: #9e8b6e;
box-shadow: 0 1px 0 0 #d8d8d8;
}

View File

@ -1,15 +1,28 @@
<div class="container"> <div class="container">
<mat-icon matSuffix class="arrow-back arrow-select" (click)="onArrowBackClick()">arrow_back</mat-icon> <mat-icon matSuffix class="arrow-back arrow-select" (click)="onArrowBackClick()">arrow_back</mat-icon>
<div *ngIf="charactersList != null && charactersList.length > 0">
<div class="primary-text header">Select character</div> <div class="primary-text header">Select character</div>
<mat-divider class="mat-divider--custom-style"></mat-divider>
<mat-list> <mat-list>
<mat-divider></mat-divider>
<mat-list-item *ngFor="let character of charactersList"> <mat-list-item *ngFor="let character of charactersList">
<mat-icon mat-list-icon>account_circle</mat-icon> <mat-icon mat-list-icon>account_circle</mat-icon>
<div mat-line>{{character.name}}</div> <div mat-line (click)="onCharacterClick(character.id)">{{character.name}}</div>
<mat-icon matSuffix class="arrow-forward arrow-select" (click)="onCharacterClick(character.id)">arrow_forward</mat-icon> <mat-icon matSuffix class="arrow-forward arrow-select" (click)="onCharacterClick(character.id)">arrow_forward</mat-icon>
<div mat-line> {{character.className}} level: {{character.level}}</div> <div mat-line> {{character.className}} level: {{character.level}}</div>
<mat-divider></mat-divider> <mat-divider class="mat-divider--custom-style"></mat-divider>
</mat-list-item> </mat-list-item>
</mat-list> </mat-list>
<div class="primary-text header">or</div>
</div>
<div class="primary-text header">Create character</div>
<mat-divider class="mat-divider--custom-style"></mat-divider>
<mat-list>
<mat-list-item>
<mat-icon mat-list-icon>account_circle</mat-icon>
<div mat-line>Create character</div>
<mat-icon matSuffix class="arrow-forward arrow-select" (click)="onCreateCharacter()">arrow_forward</mat-icon>
</mat-list-item>
</mat-list>
<mat-divider class="mat-divider--custom-style"></mat-divider>
</div> </div>

View File

@ -1,44 +1,57 @@
import {Component, OnInit} from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import {first} from 'rxjs/operators'; import { first } from 'rxjs/operators';
import {ClearUserId} from '../../store/actions/app.actions'; import { ClearUserId } from '../../store/actions/app.actions';
import {ErrorResponse} from '../../../types/ErrorResponse'; import { ErrorResponse } from '../../../types/ErrorResponse';
import {HttpErrorResponse} from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import {Store} from '@ngrx/store'; import { Store } from '@ngrx/store';
import {AppState} from '../../store/models/app-state.model'; import { AppState } from '../../store/models/app-state.model';
import {CharacterService} from '../../../services/character.service'; import { CharacterService } from '../../../services/character.service';
import {CharacterForLoginViewModel} from '../../../types/viewmodels/character-viewmodels/CharacterForLoginViewModel'; import { CharacterForLoginViewModel } from '../../../types/viewmodels/character-viewmodels/CharacterForLoginViewModel';
import {AddCharacterId} from '../../store/actions/player.action'; import { AddCharacterId } from '../../store/actions/player.action';
@Component({ @Component({
selector: 'app-select-character', selector: 'app-select-character',
templateUrl: './select-character.component.html', templateUrl: './select-character.component.html',
styleUrls: ['./select-character.component.css'] styleUrls: ['./select-character.component.css'],
}) })
export class SelectCharacterComponent implements OnInit { export class SelectCharacterComponent implements OnInit {
charactersList: CharacterForLoginViewModel[]; charactersList: CharacterForLoginViewModel[];
constructor(private router: Router, private store: Store<AppState>, private characterService: CharacterService) {} constructor(
private router: Router,
private store: Store<AppState>,
private characterService: CharacterService
) {}
ngOnInit() { ngOnInit() {
this.getUserCharactersList(); this.getUserCharactersList();
} }
getUserCharactersList() { getUserCharactersList() {
this.store.select(s => s.appStore.userId).pipe(first()).subscribe((userId) => { this.store
this.characterService.getUserCharactersList(userId).pipe(first()).subscribe((charactersList) => { .select((s) => s.appStore.userId)
this.charactersList = charactersList; .pipe(first())
}, (error: ErrorResponse | HttpErrorResponse) => { .subscribe((userId) => {
if (error instanceof HttpErrorResponse) { this.characterService
error = error.error as ErrorResponse; .getUserCharactersList(userId)
} .pipe(first())
console.error(error.message); .subscribe(
} ); (charactersList) => {
}); this.charactersList = charactersList;
},
(error: ErrorResponse | HttpErrorResponse) => {
if (error instanceof HttpErrorResponse) {
error = error.error as ErrorResponse;
}
console.error(error.message);
}
);
});
} }
onCharacterClick(characterId: number) { onCharacterClick(characterId: number) {
this.store.dispatch(new AddCharacterId({characterId})); this.store.dispatch(new AddCharacterId({ characterId }));
this.router.navigate(['player']); this.router.navigate(['player']);
} }
@ -46,4 +59,8 @@ export class SelectCharacterComponent implements OnInit {
this.store.dispatch(new ClearUserId()); this.store.dispatch(new ClearUserId());
this.router.navigate(['login']); this.router.navigate(['login']);
} }
onCreateCharacter() {
this.router.navigate(['create-character-step-1']);
}
} }

View File

@ -47,7 +47,7 @@
@media (min-width: 768px) { @media (min-width: 768px) {
.container { .container {
padding-top: 10%; padding-top: 6%;
} }
.button { .button {
margin: 20px; margin: 20px;
@ -59,3 +59,9 @@
font-size: 40px; font-size: 40px;
} }
} }
@media (min-width: 1800px) {
.container {
padding-top: 10%;
}
}

View File

@ -64,6 +64,17 @@ input {
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.container {
width: 550px;
padding-top: 0;
}
.form-container {
padding: 10px;
margin: 0px;
}
}
@media (min-width: 1800px) {
.container { .container {
width: 550px; width: 550px;
padding-top: 5%; padding-top: 5%;

View File

@ -26,7 +26,9 @@
required required
placeholder="Password" placeholder="Password"
type="password" type="password"
name="password"/> name="password"
(keyup.enter)="onLoginButtonClick()"
/>
<mat-icon matSuffix>lock</mat-icon> <mat-icon matSuffix>lock</mat-icon>
<mat-error *ngIf="signInFormGroup?.get('signIn')['controls']?.password?.hasError('required')"> <mat-error *ngIf="signInFormGroup?.get('signIn')['controls']?.password?.hasError('required')">
Password is required Password is required

View File

@ -2,7 +2,9 @@ import {Action} from '@ngrx/store';
export enum PlayerActionTypes { export enum PlayerActionTypes {
ADD_CHARACTER_ID= '[PLAYER] Add character id', ADD_CHARACTER_ID= '[PLAYER] Add character id',
CLEAR_CHARACTER_ID = '[PLAYER] Clear character id' CLEAR_CHARACTER_ID = '[PLAYER] Clear character id',
CHOOSE_TEMPLATE_TO_CREATE_CHARACTER = '[PLAYER] Choose template to create character',
CREATED_CHARACTER_FROM_TEMPLATE = '[PLAYER] Created character from template'
} }
export class AddCharacterId implements Action { export class AddCharacterId implements Action {
@ -21,4 +23,20 @@ export class ClearCharacterId implements Action {
} }
} }
export type PlayerAction = AddCharacterId | ClearCharacterId; export class ChooseTemplateToCreateCharacter implements Action {
readonly type = PlayerActionTypes.CHOOSE_TEMPLATE_TO_CREATE_CHARACTER;
constructor(public payload: {characterId: number}) {
}
}
export class CreateCharacterFromTemplate implements Action {
readonly type = PlayerActionTypes.CREATED_CHARACTER_FROM_TEMPLATE;
constructor() {
}
}
export type PlayerAction = AddCharacterId | ClearCharacterId | ChooseTemplateToCreateCharacter | CreateCharacterFromTemplate;

View File

@ -9,6 +9,10 @@ export function PlayerReducer(state: PlayerStoreModel = initialState, action: Pl
switch (action.type) { switch (action.type) {
case PlayerActionTypes.ADD_CHARACTER_ID: case PlayerActionTypes.ADD_CHARACTER_ID:
return {...state, characterId: action.payload.characterId}; return {...state, characterId: action.payload.characterId};
case PlayerActionTypes.CHOOSE_TEMPLATE_TO_CREATE_CHARACTER:
return {...state, characterId: action.payload.characterId};
case PlayerActionTypes.CREATED_CHARACTER_FROM_TEMPLATE:
return {...state};
case PlayerActionTypes.CLEAR_CHARACTER_ID: case PlayerActionTypes.CLEAR_CHARACTER_ID:
return { characterId: null}; return { characterId: null};
default: default:

View File

@ -1,16 +1,18 @@
import {Inject, Injectable} from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import {HttpClient, HttpParams} from '@angular/common/http'; import { HttpClient, HttpParams } from '@angular/common/http';
import {Observable, of, throwError} from 'rxjs'; import { Observable, of, throwError } from 'rxjs';
import {ErrorResponse} from '../types/ErrorResponse'; import { ErrorResponse } from '../types/ErrorResponse';
import {Either} from '../types/Either'; import { Either } from '../types/Either';
import {CharacterForLoginViewModel} from '../types/viewmodels/character-viewmodels/CharacterForLoginViewModel'; import { CharacterForLoginViewModel } from '../types/viewmodels/character-viewmodels/CharacterForLoginViewModel';
import {switchMap, retry} from 'rxjs/operators'; import { switchMap, retry } from 'rxjs/operators';
import {LoggedCharactersViewModel} from '../types/viewmodels/character-viewmodels/LoggedCharactersViewModel'; import { LoggedCharactersViewModel } from '../types/viewmodels/character-viewmodels/LoggedCharactersViewModel';
import {CharacterStatsViewModel} from "../types/viewmodels/character-viewmodels/CharacterStatsViewModel"; import { CharacterStatsViewModel } from '../types/viewmodels/character-viewmodels/CharacterStatsViewModel';
import { CharacterFromTemplatesViewModel } from '../types/viewmodels/character-viewmodels/CharacterFromTemplatesViewModel';
import { SuccessResponse } from '../types/SuccessResponse';
Injectable({ Injectable({
providedIn: 'root' providedIn: 'root',
}) });
export class CharacterService { export class CharacterService {
private baseUrl = 'api/character/'; private baseUrl = 'api/character/';
constructor(private http: HttpClient, @Inject('BASE_URL') baseUrl: string) { constructor(private http: HttpClient, @Inject('BASE_URL') baseUrl: string) {
@ -18,42 +20,101 @@ export class CharacterService {
} }
getLoggedCharacters(): Observable<LoggedCharactersViewModel[]> { getLoggedCharacters(): Observable<LoggedCharactersViewModel[]> {
return this.http.get<Either<LoggedCharactersViewModel[], ErrorResponse>>(this.baseUrl + 'loggedCharacters').pipe( return this.http
switchMap(response => { .get<Either<LoggedCharactersViewModel[], ErrorResponse>>(
if (response.isLeft) { this.baseUrl + 'loggedCharacters'
return of(response.left); )
} else { .pipe(
return throwError(response.right); switchMap((response) => {
} if (response.isLeft) {
}), return of(response.left);
retry(3) } else {
); return throwError(response.right);
}
}),
retry(3)
);
} }
getUserCharactersList(userId: number): Observable<CharacterForLoginViewModel[]> { getUserCharactersList(
const params = new HttpParams().set('userId', userId.toString()) userId: number
return this.http.get<Either<CharacterForLoginViewModel[], ErrorResponse>>(this.baseUrl + 'userCharactersList', {params}).pipe( ): Observable<CharacterForLoginViewModel[]> {
switchMap(response => { const params = new HttpParams().set('userId', userId.toString());
if (response.isLeft) { return this.http
return of(response.left); .get<Either<CharacterForLoginViewModel[], ErrorResponse>>(
} else { this.baseUrl + 'userCharactersList',
return throwError(response.right); { params }
} )
}) .pipe(
); switchMap((response) => {
if (response.isLeft) {
return of(response.left);
} else {
return throwError(response.right);
}
})
);
} }
getCharacterStats(characterId: number): Observable<CharacterStatsViewModel[]> { getCharacterStats(
const params = new HttpParams().set('characterId', characterId.toString()) characterId: number
return this.http.get<Either<CharacterStatsViewModel[], ErrorResponse>>( this.baseUrl + 'characterStats', {params}).pipe( ): Observable<CharacterStatsViewModel[]> {
switchMap( response => { const params = new HttpParams().set('characterId', characterId.toString());
if (response.isLeft) { return this.http
return of(response.left); .get<Either<CharacterStatsViewModel[], ErrorResponse>>(
} else { this.baseUrl + 'characterStats',
return throwError(response.right); { params }
} )
}) .pipe(
); switchMap((response) => {
if (response.isLeft) {
return of(response.left);
} else {
return throwError(response.right);
}
})
);
} }
getTemplateCharacters(): Observable<CharacterFromTemplatesViewModel[]> {
return this.http
.get<Either<CharacterFromTemplatesViewModel[], ErrorResponse>>(
this.baseUrl + 'getTemplateCharacters'
)
.pipe(
switchMap((response) => {
if (response.isLeft) {
return of(response.left);
} else {
return throwError(response.right);
}
})
);
}
createCharacterFromTemplate(
characterId: number,
userId: number,
newName: string
): Observable<SuccessResponse> {
const params = new HttpParams()
.set('characterId', characterId.toString())
.set('userId', userId.toString())
.set('newName', newName);
return this.http
.post<Either<SuccessResponse, ErrorResponse>>(
this.baseUrl + 'createCharacterFromTemplate',
null,
{ params }
)
.pipe(
switchMap((response) => {
if (response.isLeft) {
return of(response.left);
} else {
return throwError(response.right);
}
})
);
}
} }

View File

@ -41,6 +41,10 @@
opacity: 0.5; opacity: 0.5;
} }
.arrow-forward:hover {
opacity: 0.5;
}
/* icon in mat-form-field*/ /* icon in mat-form-field*/
::ng-deep mat-form-field { ::ng-deep mat-form-field {
color: #df7c0f !important; color: #df7c0f !important;

View File

@ -0,0 +1,6 @@
export interface CharacterFromTemplatesViewModel {
id: number;
name: string;
race: string;
class: number;
}

View File

@ -116,7 +116,7 @@ namespace SessionCompanion.Controllers
/// </summary> /// </summary>
/// <returns>liste podstawowych informacji o postaciach z templatki</returns> /// <returns>liste podstawowych informacji o postaciach z templatki</returns>
[HttpGet("getTemplateCharacters")] [HttpGet("getTemplateCharacters")]
public async Task<Either<ErrorResponse, List<CharacterFromTemplatesSimpleViewModel>>> GetTemplateCharacters() public async Task<Either<List<CharacterFromTemplatesSimpleViewModel>, ErrorResponse>> GetTemplateCharacters()
{ {
var races = _raceService.Get().ToList(); var races = _raceService.Get().ToList();
var classes = _classService.Get().ToList(); var classes = _classService.Get().ToList();