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;
+ }
+}