diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/app.module.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/app.module.ts index 20c56c3..85aee6f 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/app.module.ts +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/app.module.ts @@ -50,6 +50,9 @@ import { GameMasterMonstersTableComponent } from './components/game-master-monst import { MonsterService } from '../services/monster.service'; import { SpellDetailsDialogComponent } from './components/spell-details-dialog/spell-details-dialog.component'; import { GameMasterShopkeepersTableComponent } from './components/game-master-shopkeepers-table/game-master-shopkeepers-table.component'; +import { GameMasterTurntrackerComponent } from './components/game-master-turntracker/game-master-turntracker.component'; +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { ChooseMonsterDialogComponent } from './components/choose-monster-dialog/choose-monster-dialog.component'; @NgModule({ declarations: [ @@ -70,6 +73,8 @@ import { GameMasterShopkeepersTableComponent } from './components/game-master-sh GameMasterMonstersTableComponent, SpellDetailsDialogComponent, GameMasterShopkeepersTableComponent, + GameMasterTurntrackerComponent, + ChooseMonsterDialogComponent, ], imports: [ BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), @@ -98,6 +103,7 @@ import { GameMasterShopkeepersTableComponent } from './components/game-master-sh MatSortModule, MatTooltipModule, MatRadioModule, + DragDropModule, ], providers: [ UserService, @@ -119,6 +125,8 @@ import { GameMasterShopkeepersTableComponent } from './components/game-master-sh ThrowPrimaryAbilityComponent, SpellDetailsDialogComponent, GameMasterShopkeepersTableComponent, + GameMasterTurntrackerComponent, + ChooseMonsterDialogComponent, ], }) export class AppModule {} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/choose-monster-dialog/choose-monster-dialog.component.css b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/choose-monster-dialog/choose-monster-dialog.component.css new file mode 100644 index 0000000..0f1198c --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/choose-monster-dialog/choose-monster-dialog.component.css @@ -0,0 +1,41 @@ +table { + background-color: initial; +} + +mat-paginator { + background-color: initial; + color: white; +} + +::ng-deep .mat-select-arrow { + color: whitesmoke; +} + +::ng-deep .mat-select-value { + color: white; +} + +.mat-sort-header-container { + color: whitesmoke !important; +} + +.mat-form-field { + font-size: 14px; + width: 100%; +} + +td, +th { + color: whitesmoke; +} + +::ng-deep .mat-dialog-container { + background-color: #4a5867; + color: whitesmoke; + box-shadow: 0 11px 15px -7px rgba(0, 0, 0, 0.2), + 0 24px 38px 3px rgba(0, 0, 0, 0.14), 0px 5px 20px 4px #d8d8d8; +} + +::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background { + background-color: #df7c0f; +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/choose-monster-dialog/choose-monster-dialog.component.html b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/choose-monster-dialog/choose-monster-dialog.component.html new file mode 100644 index 0000000..db59d91 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/choose-monster-dialog/choose-monster-dialog.component.html @@ -0,0 +1,52 @@ +

Choose Monsters

+
+ + Filter + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Select Monsters + + + + Name {{row.name}} Armor Class {{row.armorClass}} Hit Points {{row.hitPoints}}
No data matching the filter "{{input.value}}"
+ + +
+ +
+
+ + +
diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/choose-monster-dialog/choose-monster-dialog.component.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/choose-monster-dialog/choose-monster-dialog.component.ts new file mode 100644 index 0000000..4f28018 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/choose-monster-dialog/choose-monster-dialog.component.ts @@ -0,0 +1,67 @@ +import { Component, Inject, OnInit, ViewChild } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { MonsterViewModel } from '../../../types/viewmodels/monster-viewmodels/MonsterViewModel'; +import { MonsterService } from '../../../services/monster.service'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { first } from 'rxjs/operators'; +import { MatTableDataSource } from '@angular/material/table'; +import { ErrorResponse } from '../../../types/ErrorResponse'; +import { HttpErrorResponse } from '@angular/common/http'; +import { SelectionModel } from '@angular/cdk/collections'; + +@Component({ + selector: 'app-choose-monster-dialog', + templateUrl: './choose-monster-dialog.component.html', + styleUrls: ['./choose-monster-dialog.component.css'], +}) +export class ChooseMonsterDialogComponent implements OnInit { + displayedColumns: string[] = ['select', 'name', 'armorClass', 'hitPoints']; + dataSource: MatTableDataSource; + selection = new SelectionModel(true, []); + + @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; + @ViewChild(MatSort, { static: true }) sort: MatSort; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + private monsterService: MonsterService + ) {} + + ngOnInit() { + this.monsterService + .GetAllMonsters() + .pipe(first()) + .subscribe( + (result) => { + this.dataSource = new MatTableDataSource(result); + this.dataSource.sort = this.sort; + this.dataSource.paginator = this.paginator; + }, + (error: ErrorResponse | HttpErrorResponse) => { + if (error instanceof HttpErrorResponse) { + error = error.error as ErrorResponse; + } + console.error(error.message); + } + ); + } + + AddMonsters() { + this.dialogRef.close(this.selection.selected); + } + + CloseDialog() { + this.dialogRef.close(); + } + + applyFilter(event: Event) { + const filterValue = (event.target as HTMLInputElement).value; + this.dataSource.filter = filterValue.trim().toLowerCase(); + + if (this.dataSource.paginator) { + this.dataSource.paginator.firstPage(); + } + } +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-dashboard/game-master-dashboard.component.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-dashboard/game-master-dashboard.component.ts index 3db8ef0..78f106c 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-dashboard/game-master-dashboard.component.ts +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-dashboard/game-master-dashboard.component.ts @@ -22,6 +22,7 @@ import { AppState } from '../../store/models/app-state.model'; import { Router } from '@angular/router'; import { GameMasterMonstersTableComponent } from '../game-master-monsters-table/game-master-monsters-table.component'; import { GameMasterShopkeepersTableComponent } from '../game-master-shopkeepers-table/game-master-shopkeepers-table.component'; +import { GameMasterTurntrackerComponent } from '../game-master-turntracker/game-master-turntracker.component'; @Component({ selector: 'app-game-master-dashboard', @@ -66,6 +67,12 @@ export class GameMasterDashboardComponent implements OnInit, OnDestroy { componentToDisplay: 'GameMasterMonstersTableComponent', expanded: false, }, + { + displayName: 'Turn Tracker', + iconName: 'ra ra-crossed-axes', + componentToDisplay: 'GameMasterTurntrackerComponent', + expanded: false, + }, { displayName: 'Shopkeepers', iconName: 'ra ra-wooden-sign', @@ -152,6 +159,9 @@ export class GameMasterDashboardComponent implements OnInit, OnDestroy { case 'GameMasterShopkeepersTableComponent': this.middleComponentName = GameMasterShopkeepersTableComponent; break; + case 'GameMasterTurntrackerComponent': + this.middleComponentName = GameMasterTurntrackerComponent; + break; } } diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-turntracker/game-master-turntracker.component.css b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-turntracker/game-master-turntracker.component.css new file mode 100644 index 0000000..6349419 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-turntracker/game-master-turntracker.component.css @@ -0,0 +1,79 @@ +.turn-tracker-main { + margin-left: 20%; +} + +.first { + background-color: #df7c0f !important; +} + +.turn-tracker-main > div { + width: 400px; + max-width: 100%; + margin: 0 25px 25px 0; + display: inline-block; + vertical-align: top; +} + +.turn-tracker-main > div > h2 { + color: whitesmoke; +} +.no-focus:focus { + outline: none; +} +.turn-tracker-main > div > h2 > button { + top: -7px; +} +.turn-tracker-list { + border: solid 1px #3d4751; + min-height: 81px; + background: #606f80; + border-radius: 4px; + overflow: hidden; + display: block; +} +.to-right { + float: right; + color: whitesmoke; +} +.turn-tracker-box { + padding: 20px 10px; + border-bottom: solid 1px #3d4751 !important; + color: whitesmoke; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + box-sizing: border-box; + cursor: move; + background: #606f80; + font-size: 14px; +} +.turn-tracker-box-name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.cdk-drag-preview { + box-sizing: border-box; + border-radius: 4px; + box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); +} +.character-list { + height: 81px; +} +.cdk-drag-placeholder { + opacity: 0; +} + +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.example-box:last-child { + border: none; +} + +.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-turntracker/game-master-turntracker.component.html b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-turntracker/game-master-turntracker.component.html new file mode 100644 index 0000000..6490d60 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-turntracker/game-master-turntracker.component.html @@ -0,0 +1,48 @@ +
+
+

+ Turn Tracker + +

+
+
+ + {{item.name}} + + +
+
+ +
+ +
+

+ Players characters list + +

+ +
+
+ + {{item.name}} + +
+
+
+
diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-turntracker/game-master-turntracker.component.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-turntracker/game-master-turntracker.component.ts new file mode 100644 index 0000000..00892c3 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-turntracker/game-master-turntracker.component.ts @@ -0,0 +1,145 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { + CdkDragDrop, + moveItemInArray, + transferArrayItem, +} from '@angular/cdk/drag-drop'; +import { CharacterService } from '../../../services/character.service'; +import { first } from 'rxjs/operators'; +import { MatDialog } from '@angular/material'; +import { ChooseMonsterDialogComponent } from '../choose-monster-dialog/choose-monster-dialog.component'; +import { MonsterViewModel } from '../../../types/viewmodels/monster-viewmodels/MonsterViewModel'; +import { Store } from '@ngrx/store'; +import { AddTurnTrackerList } from '../../store/actions/game-master.actions'; +import { AppState } from '../../store/models/app-state.model'; + +@Component({ + selector: 'app-game-master-turntracker', + templateUrl: './game-master-turntracker.component.html', + styleUrls: ['./game-master-turntracker.component.css'], +}) +export class GameMasterTurntrackerComponent implements OnInit, OnDestroy { + turnTrackerList: { + name: string; + characterId: number; + monsterId: number; + }[] = []; + + characterList: { + name: string; + characterId: number; + monsterId: number; + }[] = []; + + drop(event: CdkDragDrop) { + if (event.previousContainer === event.container) { + moveItemInArray( + event.container.data, + event.previousIndex, + event.currentIndex + ); + } else { + transferArrayItem( + event.previousContainer.data, + event.container.data, + event.previousIndex, + event.currentIndex + ); + } + } + constructor( + private characterService: CharacterService, + public dialog: MatDialog, + private store: Store + ) { + this.store + .select((s) => s.gameMasterStore.turnTrackerList) + .pipe(first()) + .subscribe((result) => { + if (result.length > 0) { + this.turnTrackerList = result; + } + this.GetLoggedCharacters(); + }); + } + + ngOnInit() {} + + GetLoggedCharacters() { + this.characterService + .getLoggedCharacters() + .pipe(first()) + .subscribe((result) => { + const currentCharacterIds = this.turnTrackerList.map( + (e) => e.characterId + ); + this.characterList = result + .filter((e) => !currentCharacterIds.includes(e.id)) + .map((e) => ({ + name: e.name, + characterId: e.id, + monsterId: null, + })); + }); + } + + DeleteRow(element: { name: string; characterId: number; monsterId: number }) { + if (element.characterId != null) { + this.turnTrackerList = this.turnTrackerList.filter( + (e) => e.characterId !== element.characterId + ); + } else { + this.turnTrackerList = this.turnTrackerList.filter((e) => { + return !(e.name == element.name && e.monsterId == element.monsterId); + }); + } + } + + NextTurn() { + if (this.turnTrackerList.length > 1) { + this.turnTrackerList = [ + ...this.turnTrackerList.slice(1), + this.turnTrackerList[0], + ]; + } + } + + AddMonster() { + let dialogRef = this.dialog.open(ChooseMonsterDialogComponent, { + width: '600px', + }); + + dialogRef.afterClosed().subscribe((result: MonsterViewModel[]) => { + if (result != null) { + let monstersOnList = this.turnTrackerList.filter( + (c) => c.monsterId !== null + ); + result.forEach((ele) => { + if (monstersOnList.map((e) => e.name).includes(ele.name)) { + debugger; + let maxCurrentId = Math.max( + ...monstersOnList.map((e) => e.monsterId), + 0 + ); + maxCurrentId += 1; + this.turnTrackerList = [ + ...this.turnTrackerList, + { name: ele.name, characterId: null, monsterId: maxCurrentId }, + ]; + } else { + this.turnTrackerList = [ + ...this.turnTrackerList, + { name: ele.name, characterId: null, monsterId: 1 }, + ]; + } + }); + } + }); + } + + ngOnDestroy() { + this.store.dispatch( + new AddTurnTrackerList({ turnTrackerList: this.turnTrackerList }) + ); + } +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/store/actions/game-master.actions.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/store/actions/game-master.actions.ts new file mode 100644 index 0000000..11fdeaf --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/store/actions/game-master.actions.ts @@ -0,0 +1,21 @@ +import { Action } from '@ngrx/store'; + +export enum GameMasterActionTypes { + ADD_TURN_TRACKER_LIST = '[GAME MASTER] Add turn tracker list', +} + +export class AddTurnTrackerList implements Action { + readonly type = GameMasterActionTypes.ADD_TURN_TRACKER_LIST; + + constructor( + public payload: { + turnTrackerList: { + name: string; + characterId: number; + monsterId: number; + }[]; + } + ) {} +} + +export type GameMasterAction = AddTurnTrackerList; diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/store/models/app-state.model.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/store/models/app-state.model.ts index fe999ff..4e26d38 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/store/models/app-state.model.ts +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/store/models/app-state.model.ts @@ -1,15 +1,19 @@ -import {AppStoreModel} from './app-store.model'; -import {ActionReducerMap} from '@ngrx/store'; -import {AppReducer} from '../reducers/app.reducer'; -import {PlayerStoreModel} from './player-store.model'; -import {PlayerReducer} from '../reducers/player.reducer'; +import { AppStoreModel } from './app-store.model'; +import { ActionReducerMap } from '@ngrx/store'; +import { AppReducer } from '../reducers/app.reducer'; +import { PlayerStoreModel } from './player-store.model'; +import { PlayerReducer } from '../reducers/player.reducer'; +import { GameMasterStoreModel } from './game-master-store.model'; +import { GameMasterReducer } from '../reducers/game-master.reducer'; export interface AppState { appStore: AppStoreModel; playerStore: PlayerStoreModel; + gameMasterStore: GameMasterStoreModel; } export const reducers: ActionReducerMap = { appStore: AppReducer, playerStore: PlayerReducer, + gameMasterStore: GameMasterReducer, }; diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/store/models/game-master-store.model.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/store/models/game-master-store.model.ts new file mode 100644 index 0000000..666c3a8 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/store/models/game-master-store.model.ts @@ -0,0 +1,7 @@ +export interface GameMasterStoreModel { + turnTrackerList: { + name: string; + characterId: number; + monsterId: number; + }[]; +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/store/reducers/game-master.reducer.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/store/reducers/game-master.reducer.ts new file mode 100644 index 0000000..f43afd6 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/store/reducers/game-master.reducer.ts @@ -0,0 +1,21 @@ +import { GameMasterStoreModel } from '../models/game-master-store.model'; +import { + GameMasterAction, + GameMasterActionTypes, +} from '../actions/game-master.actions'; + +const initialState: GameMasterStoreModel = { + turnTrackerList: [], +}; + +export function GameMasterReducer( + state: GameMasterStoreModel = initialState, + action: GameMasterAction +) { + switch (action.type) { + case GameMasterActionTypes.ADD_TURN_TRACKER_LIST: + return { ...state, turnTrackerList: action.payload.turnTrackerList }; + default: + return state; + } +}