issue-5-gamesystems #6

Merged
sebastian merged 24 commits from issue-5-gamesystems into main 2024-02-10 13:30:37 +01:00
45 changed files with 1598 additions and 67 deletions

View File

@ -57,7 +57,7 @@ function createWindow(): BrowserWindow {
{
label: "Gamesystem",
click: () => {
win!.webContents.send('context-menu', "new-location");
win!.webContents.send('context-menu', "new-gamesystem");
}
},
{

View File

@ -0,0 +1,53 @@
import { BrowserContext, ElectronApplication, Page, _electron as electron } from 'playwright';
import { test, expect } from '@playwright/test';
import * as PATH from 'path';
import {GameModel} from "../../../src/app/game-model/GameModel";
import {Gamesystem} from "../../src/app/game-model/gamesystems/Gamesystem";
import {ScriptAccount} from "../../../src/app/game-model/scriptAccounts/ScriptAccount";
import {ModelComponentType} from "../../../src/app/game-model/ModelComponentType";
import {SimpleGamesystem} from "../../../src/app/game-model/gamesystems/SimpleGamesystem";
import exp = require("node:constants");
import {end} from "electron-debug";
import {GamesystemTrainer} from "./GamesystemTrainer";
import {ProductGamesystem} from "../../../src/app/game-model/gamesystems/ProductGamesystem";
test.describe('Test Create Gamesystems', () => {
test('Test creating gamesystem with invalid name', async => {
const gameModel = GamesystemTrainer.givenEmptyGameModel();
let result = gameModel.createGamesystem(undefined, undefined);
expect(result).toBeUndefined();
result = gameModel.createGamesystem(null, undefined);
expect(result).toBeUndefined();
})
test("Test creating gamesystem with valid name but without parent", async => {
const gameModel = GamesystemTrainer.givenEmptyGameModel();
let result = gameModel.createGamesystem(GamesystemTrainer.SIMPLEGAMESYSTEMNAME, undefined);
expect(result).toBeDefined();
expect(gameModel.gamesystems.length).toEqual(1);
expect(gameModel.findGamesystem(GamesystemTrainer.SIMPLEGAMESYSTEMNAME)).toBeDefined();
})
test("Test creating Gamesystem with valid name but with Product Parent", async => {
const gameModel = GamesystemTrainer.givenGameModelWithProductGamesytemOnTopLayer();
let result = gameModel.createGamesystem(GamesystemTrainer.SIMPLEGAMESYSTEM_LEAF_LEFT, GamesystemTrainer.TOP_PRODUCT_GAMESYSTEM_NAME);
expect(result).toBeDefined();
expect(result.parentGamesystem!.componentName).toEqual(GamesystemTrainer.TOP_PRODUCT_GAMESYSTEM_NAME);
expect(result.parentGamesystem!.innerGamesystems.length).toEqual(3);
expect(result.parentGamesystem!.innerGamesystems.includes(result)).toBeTruthy();
})
test("Test creating Gamesystem with valid name but with Simple Parent", async() => {
const gameModel = GamesystemTrainer.givenGameModelWithSimpleGamesystemOnTopLayer();
let result = gameModel.createGamesystem(GamesystemTrainer.SIMPLEGAMESYSTEM_LEAF_LEFT, GamesystemTrainer.SIMPLEGAMESYSTEMNAME);
expect(result).toBeDefined();
expect(gameModel.gamesystems.length).toEqual(1);
expect(gameModel.gamesystems[0]).toBeInstanceOf(ProductGamesystem);
expect(gameModel.gamesystems[0]).toEqual(result.parentGamesystem);
expect((gameModel.gamesystems[0] as ProductGamesystem).innerGamesystems.length).toEqual(1);
expect((gameModel.gamesystems[0] as ProductGamesystem).innerGamesystems.includes(result)).toBeTruthy();
})
});

View File

@ -0,0 +1,50 @@
import { BrowserContext, ElectronApplication, Page, _electron as electron } from 'playwright';
import { test, expect } from '@playwright/test';
import * as PATH from 'path';
import {GameModel} from "../../../src/app/game-model/GameModel";
import {Gamesystem} from "../../src/app/game-model/gamesystems/Gamesystem";
import {ScriptAccount} from "../../../src/app/game-model/scriptAccounts/ScriptAccount";
import {ModelComponentType} from "../../../src/app/game-model/ModelComponentType";
import {SimpleGamesystem} from "../../../src/app/game-model/gamesystems/SimpleGamesystem";
import exp = require("node:constants");
import {end} from "electron-debug";
import {GamesystemTrainer} from "./GamesystemTrainer";
test.describe('Test Find Gamesystems', () => {
const GAMEMODELNAME: string = "GameModel";
const SIMPLEGAMESYSTEM_NAME: string = "Simple Gamesystem";
test('Find null or undefined Gamesystem', async () => {
const gameModel = new GameModel(GAMEMODELNAME);
expect(gameModel.findGamesystem(null)).toBeUndefined();
expect(gameModel.findGamesystem(undefined)).toBeUndefined();
})
test('Find non existend Gamesystem', async () => {
const gameModel = new GameModel(GAMEMODELNAME);
expect(gameModel.findGamesystem("Gamesystem")).toBeUndefined();
})
test("Find existend simple Gamesystem on top layer", async () => {
const gameModel = new GameModel(GAMEMODELNAME);
const gamesystem = new SimpleGamesystem(SIMPLEGAMESYSTEM_NAME);
gameModel.addGamesystem(gamesystem);
expect(gameModel.findGamesystem(SIMPLEGAMESYSTEM_NAME)).toBeDefined();
expect(gameModel.findGamesystem(SIMPLEGAMESYSTEM_NAME)).toEqual(gamesystem);
})
test('Find existent product gamesystem on top layer', async () => {
const gameModel = GamesystemTrainer.givenGameModelWithProductGamesytemOnTopLayer();
const result = gameModel.findGamesystem(GamesystemTrainer.TOP_PRODUCT_GAMESYSTEM_NAME);
expect(result).toBeDefined();
})
test('Find existent simple gamesystem on lower layer', async () => {
const gameModel = GamesystemTrainer.givenGameModelWithProductGamesytemOnTopLayer();
const result = gameModel.findGamesystem(GamesystemTrainer.SIMPLEGAMESYSTEMNAME);
expect(result).toBeDefined();
})
});

View File

@ -0,0 +1,55 @@
import {GameModel} from "../../../src/app/game-model/GameModel";
import {SimpleGamesystem} from "../../../src/app/game-model/gamesystems/SimpleGamesystem";
import {ProductGamesystem} from "../../../src/app/game-model/gamesystems/ProductGamesystem";
export class GamesystemTrainer {
static GAMEMODELNAME: string = "GAMEMODEL";
static SIMPLEGAMESYSTEMNAME: string = "SIMPLE GAMESYSTEM";
static TOP_PRODUCT_GAMESYSTEM_NAME: string = "Top Product Gamesystem"
static SIMPLEGAMESYSTEM2: string = "Simple Gamesystem Leaf 2";
static SIMPLEGAMESYSTEM_LEAF_LEFT: string = "Leaf Gamesystem Left"
static SIMPLEGAMESYSTEM_LEAF_RIGHT: string = "Leaf Gamesystem Right";
static givenEmptyGameModel() {
return new GameModel(GamesystemTrainer.GAMEMODELNAME);
}
static givenGameModelWithSimpleGamesystemOnTopLayer() {
const gameModel = new GameModel(GamesystemTrainer.GAMEMODELNAME);
const gamesytem = new SimpleGamesystem(GamesystemTrainer.SIMPLEGAMESYSTEMNAME);
gameModel.addGamesystem(gamesytem);
return gameModel;
}
static givenGameModelWithProductGamesytemOnTopLayer() {
const gameModel = new GameModel(GamesystemTrainer.GAMEMODELNAME);
const productGamesystem = new ProductGamesystem(this.TOP_PRODUCT_GAMESYSTEM_NAME);
const leaf1 = new SimpleGamesystem(this.SIMPLEGAMESYSTEMNAME);
const leaf2 = new SimpleGamesystem(this.SIMPLEGAMESYSTEM2);
productGamesystem.innerGamesystems.push(leaf1);
productGamesystem.innerGamesystems.push(leaf2);
gameModel.addGamesystem(productGamesystem);
return gameModel;
}
static givenGameModelWithProductGamesystemOnLowerLayer() {
const gameModel = new GameModel(GamesystemTrainer.GAMEMODELNAME);
const top_productGamesystem = new ProductGamesystem(this.TOP_PRODUCT_GAMESYSTEM_NAME);
const leaf1 = new ProductGamesystem(this.SIMPLEGAMESYSTEMNAME);
const leaf2 = new SimpleGamesystem(this.SIMPLEGAMESYSTEM2);
top_productGamesystem.innerGamesystems.push(leaf1);
top_productGamesystem.innerGamesystems.push(leaf2);
gameModel.addGamesystem(top_productGamesystem);
const leaf_1_1 = new SimpleGamesystem(this.SIMPLEGAMESYSTEM_LEAF_LEFT);
leaf1.addChildGamesystem(leaf_1_1);
const leaf_1_2 = new SimpleGamesystem(this.SIMPLEGAMESYSTEM_LEAF_RIGHT);
leaf1.addChildGamesystem(leaf_1_2);
}
}

View File

@ -0,0 +1,172 @@
import { BrowserContext, ElectronApplication, Page, _electron as electron } from 'playwright';
import { test, expect } from '@playwright/test';
import * as PATH from 'path';
import {GameModel} from "../../../src/app/game-model/GameModel";
import {Gamesystem} from "../../src/app/game-model/gamesystems/Gamesystem";
import {ScriptAccount} from "../../../src/app/game-model/scriptAccounts/ScriptAccount";
import {ModelComponentType} from "../../../src/app/game-model/ModelComponentType";
import {SimpleGamesystem} from "../../../src/app/game-model/gamesystems/SimpleGamesystem";
import exp = require("node:constants");
import {end} from "electron-debug";
test.describe('Test SimpleGamesystem', () => {
test("Create SimpleState", async () => {
const gamesystem = new SimpleGamesystem();
let state = gamesystem.createState("Test", "Test2");
expect(state.stateLabel).toEqual("Test");
expect(state.stateDescription).toEqual("Test2");
expect(state.incomingTransitions.length).toEqual(0);
expect(state.outgoingTransitions.length).toEqual(0);
expect(gamesystem.states.includes(state)).toBeTruthy();
state = gamesystem.createState(null, null);
expect(state).toBeUndefined();
expect(gamesystem.states.includes(state)).toBeFalsy();
state = gamesystem.createState(null, "test2");
expect(state).toBeUndefined()
expect(gamesystem.states.includes(state)).toBeFalsy();
state = gamesystem.createState("test2", null);
expect(state).toBeDefined();
expect(state.stateLabel).toEqual("test2");
expect(state.stateDescription).toEqual("");
expect(gamesystem.states.includes(state)).toBeTruthy();
state = gamesystem.createState(undefined, "State");
expect(state).toBeUndefined();
expect(gamesystem.states.includes(state)).toBeFalsy();
state = gamesystem.createState("Test3", undefined);
expect(state).toBeDefined();
expect(state.stateLabel).toEqual("Test3");
expect(state.stateDescription).toEqual("");
expect(gamesystem.states.includes(state)).toBeTruthy();
state = gamesystem.createState("", "");
expect(state).toBeDefined();
expect(state.stateLabel).toEqual("");
expect(state.stateDescription).toEqual("");
expect(gamesystem.states.includes(state)).toBeTruthy();
state = gamesystem.createState("Test3", "");
expect(state).toBeUndefined();
expect(gamesystem.states.includes(state)).toBeFalsy();
})
test("Create SimpleTransition", async () => {
const gamesystem = new SimpleGamesystem();
const startingState = gamesystem.createState("StartingState", "")!;
const endingState = gamesystem.createState("EndingState", "")!
let transition = gamesystem.createTransition(startingState, endingState);
expect(transition).toBeDefined();
expect(transition.startingState).toEqual(startingState);
expect(transition.endingState).toEqual(endingState);
expect(startingState.outgoingTransitions.includes(transition)).toBeTruthy();
expect(endingState.incomingTransitions.includes(transition)).toBeTruthy();
expect(gamesystem.transitions.includes(transition)).toBeTruthy();
transition = gamesystem.createTransition(null, null);
expect(transition).toBeUndefined();
transition = gamesystem.createTransition(null, endingState);
expect(transition).toBeUndefined();
transition = gamesystem.createTransition(undefined, undefined);
expect(transition).toBeUndefined();
transition = gamesystem.createTransition(undefined, endingState);
expect(transition).toBeUndefined();
transition = gamesystem.createTransition(startingState, null);
expect(transition).toBeUndefined();
transition = gamesystem.createTransition(startingState, undefined);
expect(transition).toBeUndefined();
transition = gamesystem.createTransition(startingState, startingState);
expect(transition).toBeUndefined();
transition = gamesystem.createTransition(startingState, endingState);
expect(transition).toBeUndefined();
})
test("Remove SimpleState", async () => {
const gamesystem = new SimpleGamesystem("Test");
const state1 = gamesystem.createState("State1", "");
const state1_delete = gamesystem.removeState(state1);
expect(state1_delete).toBeTruthy();
expect(gamesystem.states.includes(state1)).toBeFalsy();
let startingState = gamesystem.createState("Start", "End");
let endingState = gamesystem.createState("End", "");
let transition = gamesystem.createTransition(startingState, endingState);
let result = gamesystem.removeState(startingState);
expect(result).toBeTruthy();
expect(gamesystem.states.includes(startingState)).toBeFalsy();
expect(gamesystem.states.includes(endingState)).toBeTruthy();
expect(gamesystem.transitions.length).toEqual(0);
startingState = gamesystem.createState("Start");
transition = gamesystem.createTransition(startingState, endingState);
gamesystem.removeState(endingState);
expect(result).toBeTruthy();
expect(gamesystem.states.includes(startingState)).toBeTruthy();
expect(gamesystem.states.includes(endingState)).toBeFalsy();
expect(gamesystem.transitions.length).toEqual(0);
endingState = gamesystem.createState("End");
transition = gamesystem.createTransition(startingState, endingState);
const testingState = gamesystem.createState("TestingState", "");
result = gamesystem.removeState(testingState);
expect(result).toBeTruthy();
expect(gamesystem.transitions.includes(transition)).toBeTruthy();
const gamesystem2 = new SimpleGamesystem("test2");
const state2 = gamesystem2.createState("Test", "");
result = gamesystem.removeState(state2);
expect(result).toBeFalsy();
result = gamesystem.removeState(null);
expect(result).toBeFalsy();
result = gamesystem.removeState(undefined);
expect(result).toBeFalsy();
})
test("Remove SimpleTransition", async () => {
const gamesystem = new SimpleGamesystem("Gamesystem");
const A = gamesystem.createState("A");
const B = gamesystem.createState("B");
let AB = gamesystem.createTransition(A, B);
let result = gamesystem.removeTransition(AB);
expect(result).toBeTruthy();
expect(gamesystem.transitions.includes(AB)).toBeFalsy();
AB = gamesystem.createTransition(A, B);
const C = gamesystem.createState("C");
const D = gamesystem.createState("D");
let CD = gamesystem.createTransition(C, D);
result = gamesystem.removeTransition(AB);
expect(result).toBeTruthy();
expect(gamesystem.transitions.includes(AB)).toBeFalsy();
expect(gamesystem.transitions.includes(CD)).toBeTruthy();
let BA = gamesystem.createTransition(B, A);
AB = gamesystem.createTransition(A, B);
result = gamesystem.removeTransition(AB);
expect(result).toBeTruthy();
expect(gamesystem.transitions.includes(AB)).toBeFalsy();
expect(gamesystem.transitions.includes(BA)).toBeTruthy();
result = gamesystem.removeTransition(AB);
expect(result).toBeFalsy();
})
});

View File

@ -21,24 +21,24 @@ test.describe('Test ScriptAccounts', () => {
test("Test Adding ScriptAccounts", async () => {
const gameModel: GameModel = new GameModel("GameModel");
let scriptAccount =gameModel.addScriptAccount("ScriptAccount");
let scriptAccount =gameModel.createScriptAccount("ScriptAccount");
expect(scriptAccount).toBeDefined();
expect(gameModel.scriptAccounts.length).toEqual(1);
expect(gameModel.scriptAccounts.includes(scriptAccount)).toBeTruthy();
//Test adding scriptAccount with already existing name
const scriptAccount2 = gameModel.addScriptAccount("ScriptAccount")
const scriptAccount2 = gameModel.createScriptAccount("ScriptAccount")
expect(scriptAccount2).toBeUndefined();
expect(gameModel.scriptAccounts.length).toEqual(1);
//Test for adding invalid names as scriptaccount names (null/undefined/empty)
let result = gameModel.addScriptAccount(null);
let result = gameModel.createScriptAccount(null);
expect(result).toBeUndefined();
expect(gameModel.scriptAccounts.length).toEqual(1);
result = gameModel.addScriptAccount(undefined);
result = gameModel.createScriptAccount(undefined);
expect(result).toBeUndefined();
expect(gameModel.scriptAccounts.length).toEqual(1);
result = gameModel.addScriptAccount("");
result = gameModel.createScriptAccount("");
expect(result).toBeUndefined();
expect(gameModel.scriptAccounts.length).toEqual(1);
})
@ -50,7 +50,7 @@ test.describe('Test ScriptAccounts', () => {
gameModel.removeScriptAccount(scriptAccount);
expect(gameModel.scriptAccounts.length).toEqual(0);
scriptAccount = gameModel.addScriptAccount("ScriptAccount");
scriptAccount = gameModel.createScriptAccount("ScriptAccount");
gameModel.removeScriptAccount(scriptAccount);
expect(gameModel.scriptAccounts.length).toEqual(0);
@ -60,8 +60,8 @@ test.describe('Test ScriptAccounts', () => {
gameModel.removeScriptAccount(null);
expect(gameModel.scriptAccounts.length).toEqual(0);
scriptAccount = gameModel.addScriptAccount(scriptAccount);
let scriptAccount2 = gameModel.addScriptAccount("ScriptAccount 2");
scriptAccount = gameModel.createScriptAccount(scriptAccount);
let scriptAccount2 = gameModel.createScriptAccount("ScriptAccount 2");
gameModel.removeScriptAccount(scriptAccount);
expect(gameModel.scriptAccounts.length).toEqual(1);

View File

@ -2,6 +2,8 @@
<div class="full-height-container">
<button mat-icon-button class="small-icon-button" [ngClass]="openContent === ModelComponentType.SCRIPTACCOUNT ? 'selected':''"
(click)="openScriptAccountsOverview()"><mat-icon>inventory_2</mat-icon></button>
<button mat-icon-button class="small-icon-button" [ngClass]="openContent === ModelComponentType.GAMESYTEM ? 'selected':''"
(click)="openGamesystemsOverview()"><mat-icon>manufacturing</mat-icon></button>
</div>
@ -17,13 +19,15 @@
<button mat-icon-button class="small-icon-button close-sidenav-btn" (click)="closeContentOverview()"><mat-icon>close</mat-icon></button>
<mat-menu #contentMenu="matMenu">
<button mat-menu-item (click)="openScriptAccountsOverview()">{{ModelComponentTypeUtillities.toString(ModelComponentType.SCRIPTACCOUNT)}}</button>
<button mat-menu-item (click)="openGamesystemsOverview()">{{ModelComponentTypeUtillities.toString(ModelComponentType.GAMESYTEM)}}</button>
</mat-menu>
</div>
<app-script-account-overview #scriptAccountOverview [gameModel]="gameModel" (onOpenScriptAccount)="openModelComponent($event)"></app-script-account-overview>
<app-script-account-overview *ngIf="openContent == ModelComponentType.SCRIPTACCOUNT" #scriptAccountOverview [gameModel]="gameModel" (onOpenScriptAccount)="openModelComponent($event)"></app-script-account-overview>
<app-gamescript-overview *ngIf="openContent == ModelComponentType.GAMESYTEM" #gamesystemOverview [gameModel]="gameModel" (openGamesystemEditor)="openModelComponent($event)"></app-gamescript-overview>
</mat-drawer>
<div class="example-sidenav-content">
<app-editor #editor></app-editor>
<app-editor #editor (onModelNameUpdate)="onModelNameUpdate()"></app-editor>
</div>
</mat-drawer-container>

View File

@ -13,6 +13,8 @@ import {
import {MatDialog} from "@angular/material/dialog";
import {DeleteConfirmationDialogComponent} from "./delete-confirmation-dialog/delete-confirmation-dialog.component";
import {ScriptAccount} from "./game-model/scriptAccounts/ScriptAccount";
import {GamescriptOverviewComponent} from "./side-overviews/gamescript-overview/gamescript-overview.component";
import {SimpleGamesystem} from "./game-model/gamesystems/SimpleGamesystem";
@Component({
selector: 'app-root',
@ -25,6 +27,7 @@ export class AppComponent implements OnInit{
@ViewChild('drawer') drawer: MatDrawerContainer|undefined
@ViewChild('editor') editor: EditorComponent|undefined
@ViewChild('scriptAccountOverview') scriptAccountOverview: ScriptAccountOverviewComponent | undefined
@ViewChild('gamesystemOverview') gamesystemOverview: GamescriptOverviewComponent | undefined
gameModel: GameModel | undefined
@ -42,29 +45,89 @@ export class AppComponent implements OnInit{
electronService.ipcRenderer.on('context-menu', (event: any, message: string) => {
this.zone.run(() => {
if(message == "edit") {
if(this.openContent == ModelComponentType.SCRIPTACCOUNT && this.scriptAccountOverview != undefined && this.scriptAccountOverview.selectedScriptAccount != undefined) {
this.editor!.openGameModelComponent(this.scriptAccountOverview.selectedScriptAccount!);
}
} else if(message == "delete") {
const affectedModelComponent = this.getSelectedModelComponent();
const dialogRef = this.dialog.open(DeleteConfirmationDialogComponent, {data: affectedModelComponent, minWidth: "400px"});
dialogRef.afterClosed().subscribe(res => {
if(res != undefined && res) {
if(affectedModelComponent instanceof ScriptAccount) {
this.gameModel!.removeScriptAccount(affectedModelComponent);
}
}
})
}
})
this.onContextMenuMessageRecieved(message);
});
})
} else {
console.log('Run in browser');
}
}
onContextMenuMessageRecieved(message: string) {
if(message == "edit") {
this.onEditModelComponent();
} else if(message == "delete") {
this.onDeleteModelComponent();
} else if(message.startsWith("new")) {
const splittedMessage = message.split("-");
const modelComponentType = ModelComponentTypeUtillities.fromString(splittedMessage[1]);
if(modelComponentType != undefined) {
this.onCreateModelComponent(modelComponentType);
} else {
console.log("[ERROR] [App-Component] Unknown Context-Menu Command!")
}
}
}
private onEditModelComponent() {
switch (this.openContent!) {
case ModelComponentType.SCRIPTACCOUNT: {
if(this.scriptAccountOverview!.selectedScriptAccount != undefined) {
this.editor!.openGameModelComponent(this.scriptAccountOverview!.selectedScriptAccount);
}
} break;
case ModelComponentType.GAMESYTEM: {
if(this.gamesystemOverview!.selectedGamesystem != undefined) {
const gamesystem = this.gameModel!.findGamesystem(this.gamesystemOverview!.selectedGamesystemName!);
this.editor!.openGameModelComponent(gamesystem!);
}
} break
}
}
private onDeleteModelComponent() {
const affectedModelComponent = this.getSelectedModelComponent();
const dialogRef = this.dialog.open(DeleteConfirmationDialogComponent, {data: affectedModelComponent, minWidth: "400px"});
dialogRef.afterClosed().subscribe(res => {
if(res != undefined && res) {
if(affectedModelComponent instanceof ScriptAccount) {
this.gameModel!.removeScriptAccount(affectedModelComponent);
}
}
})
}
private onCreateModelComponent(modelComponentType: ModelComponentType) {
switch (modelComponentType) {
case ModelComponentType.SCRIPTACCOUNT: this.onCreateNewScriptAccount(); break
case ModelComponentType.GAMESYTEM: this.onCreateNewGamesystem(); break
}
}
private onCreateNewScriptAccount() {
const createdScriptAccount = this.gameModel!.createScriptAccount("New ScriptAccount");
if(createdScriptAccount != undefined) {
this.editor?.openGameModelComponent(createdScriptAccount);
} else {
console.log("[DEBUG] [App-Component] ScriptAccount could not be created (Name not unique)");
}
}
private onCreateNewGamesystem() {
let parentGamesystemName = undefined
if(this.openContent != ModelComponentType.GAMESYTEM) {
this.openGamesystemsOverview();
} else {
parentGamesystemName = this.gamesystemOverview!.selectedGamesystemName;
}
const createdGamesystem = this.gameModel!.createGamesystem("New Gamesystem", parentGamesystemName);
if(createdGamesystem != undefined) {
this.gamesystemOverview!.refresh();
this.editor?.openGameModelComponent(createdGamesystem!);
}
}
private getSelectedModelComponent(): ModelComponent | undefined {
if(this.openContent == ModelComponentType.SCRIPTACCOUNT) {
if(this.scriptAccountOverview != undefined) {
@ -78,8 +141,19 @@ export class AppComponent implements OnInit{
ngOnInit() {
this.gameModel = new GameModel("No More");
this.gameModel.addScriptAccount("Temperature");
this.gameModel.addScriptAccount("Luftfeuchtigkeit");
this.gameModel.createScriptAccount("Temperature");
this.gameModel.createScriptAccount("Luftfeuchtigkeit");
const weather = new SimpleGamesystem("Weather");
const season = new SimpleGamesystem("Season");
const springState = season.createState("Spring", "Spring, also known as springtime, is one of the four temperate seasons, succeeding winter and preceding summer.");
const summerState = season.createState("Summer", "Summer is the hottest and brightest of the four temperate seasons, occurring after spring and before autumn. ");
season.createTransition(springState!, summerState!);
this.gameModel.addGamesystem(weather)
this.gameModel.addGamesystem(season);
}
openScriptAccountsOverview() {
@ -87,6 +161,11 @@ export class AppComponent implements OnInit{
this.drawer!.open();
}
openGamesystemsOverview() {
this.openContent = ModelComponentType.GAMESYTEM;
this.drawer!.open();
}
protected readonly ModelComponentType = ModelComponentType;
closeContentOverview() {
@ -102,6 +181,11 @@ export class AppComponent implements OnInit{
} else {
console.log("[WARN] [App.Component] Editor is undefined")
}
}
onModelNameUpdate() {
if(this.openContent == ModelComponentType.GAMESYTEM) {
this.gamesystemOverview!.refresh();
}
}
}

View File

@ -14,7 +14,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {MatIcon} from "@angular/material/icon";
import {MatToolbar} from "@angular/material/toolbar";
import {MatButton, MatIconButton, MatMiniFabButton} from "@angular/material/button";
import {MatError, MatFormField, MatLabel} from "@angular/material/form-field";
import {MatError, MatFormField, MatHint, MatLabel} from "@angular/material/form-field";
import {MatInput} from "@angular/material/input";
import {MatDrawer, MatDrawerContainer} from "@angular/material/sidenav";
import {MatMenu, MatMenuItem, MatMenuTrigger} from "@angular/material/menu";
@ -28,6 +28,29 @@ import {ScriptAccountEditorComponent} from "./editor/script-account-editor/scrip
import {ModelComponentEditorComponent} from "./editor/model-component-editor/model-component-editor.component";
import {DeleteConfirmationDialogComponent} from "./delete-confirmation-dialog/delete-confirmation-dialog.component";
import {MatDialogActions, MatDialogContent, MatDialogTitle} from "@angular/material/dialog";
import {GamescriptOverviewComponent} from "./side-overviews/gamescript-overview/gamescript-overview.component";
import {MatTree, MatTreeModule} from "@angular/material/tree";
import {GamesystemEditorComponent} from "./editor/gamesystem-editor/gamesystem-editor.component";
import {
SimpleGamesystemEditorComponent
} from "./editor/gamesystem-editor/simple-gamesystem-editor/simple-gamesystem-editor.component";
import {
SimpleStateEditorComponent
} from "./editor/gamesystem-editor/state-editor/simple-state-editor/simple-state-editor.component";
import {
MatCell,
MatCellDef,
MatColumnDef,
MatHeaderCell,
MatHeaderCellDef,
MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef,
MatTable
} from "@angular/material/table";
import {MatCheckbox} from "@angular/material/checkbox";
import {
SimpleTransitionEditorComponent
} from "./editor/gamesystem-editor/transition-editor/simple-transition-editor/simple-transition-editor.component";
import {MatOption, MatSelect} from "@angular/material/select";
// AoT requires an exported function for factories
const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new TranslateHttpLoader(http, './assets/i18n/', '.json');
@ -39,7 +62,12 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new Transl
EditorComponent,
ScriptAccountEditorComponent,
ModelComponentEditorComponent,
DeleteConfirmationDialogComponent
DeleteConfirmationDialogComponent,
GamescriptOverviewComponent,
GamesystemEditorComponent,
SimpleGamesystemEditorComponent,
SimpleStateEditorComponent,
SimpleTransitionEditorComponent
],
imports: [
BrowserModule,
@ -79,6 +107,21 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new Transl
MatDialogContent,
MatDialogActions,
MatMiniFabButton,
MatTreeModule,
MatTable,
MatColumnDef,
MatHeaderCell,
MatHeaderCellDef,
MatCellDef,
MatCell,
MatHeaderRow,
MatRow,
MatHeaderRowDef,
MatRowDef,
MatCheckbox,
MatSelect,
MatOption,
MatHint
],
providers: [],
bootstrap: [AppComponent]

View File

@ -1,13 +1,17 @@
<mat-tab-group>
<mat-tab *ngFor="let modelComponent of gameModelComponents">
<ng-template mat-tab-label>
<mat-icon class="example-tab-icon unsaved" [ngClass]="modelComponent.unsaved? 'unsaved':'saved'">inventory_2</mat-icon>
<mat-icon class="example-tab-icon unsaved" *ngIf="modelComponent.type === ModelComponentType.SCRIPTACCOUNT" [ngClass]="modelComponent.unsaved? 'unsaved':'saved'">inventory_2</mat-icon>
<mat-icon class="example-tab-icon unsaved" *ngIf="modelComponent.type === ModelComponentType.GAMESYTEM" [ngClass]="modelComponent.unsaved? 'unsaved':'saved'">code</mat-icon>
<span [ngClass]="modelComponent.unsaved? 'unsaved':'saved'">{{modelComponent.componentName}}</span>
<button class="content-label close-btn" mat-icon-button (click)="closeGameModelComponent(modelComponent)"><mat-icon>close</mat-icon></button>
</ng-template>
<app-model-component-editor [modelComponent]="modelComponent"></app-model-component-editor>
<app-model-component-editor [modelComponent]="modelComponent" (onModelNameUpdated)="onModelNameUpdate()"></app-model-component-editor>
<app-script-account-editor *ngIf="modelComponent.type === ModelComponentType.SCRIPTACCOUNT"
[scriptAccount]="convertModelComponentToScriptAccount(modelComponent)"></app-script-account-editor>
<app-gamesystem-editor *ngIf="modelComponent.type == ModelComponentType.GAMESYTEM"
[gamesystem]="convertModelComponentToGamesystem(modelComponent)"></app-gamesystem-editor>
</mat-tab>
</mat-tab-group>

View File

@ -1,8 +1,11 @@
import {Component, Input} from '@angular/core';
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {GameModel} from "../game-model/GameModel";
import {ModelComponent} from "../game-model/ModelComponent";
import {ModelComponentType} from "../game-model/ModelComponentType";
import {ScriptAccount} from "../game-model/scriptAccounts/ScriptAccount";
import {Gamesystem} from "../game-model/gamesystems/Gamesystem";
import {State} from "../game-model/gamesystems/State";
import {Transition} from "../game-model/gamesystems/Transition";
@Component({
selector: 'app-editor',
@ -11,6 +14,7 @@ import {ScriptAccount} from "../game-model/scriptAccounts/ScriptAccount";
})
export class EditorComponent {
gameModelComponents: ModelComponent[] = [];
@Output("onModelNameUpdate") onModelNameUpdateEmitter = new EventEmitter<boolean>();
openGameModelComponent(gameModelComponent: ModelComponent) {
if(!this.gameModelComponents.includes(gameModelComponent)) {
@ -28,5 +32,15 @@ export class EditorComponent {
}
}
convertModelComponentToGamesystem(modelComponent: ModelComponent) {
if(modelComponent instanceof Gamesystem) {
return modelComponent as Gamesystem<State<any>, Transition<any>>;
}
}
protected readonly ModelComponentType = ModelComponentType;
onModelNameUpdate() {
this.onModelNameUpdateEmitter.emit(true);
}
}

View File

@ -0,0 +1 @@
<app-simple-gamesystem-editor *ngIf="isSimpleGamesystem()" [simpleGamesystem]="convertGamesystemToSimpleGamesystem()"></app-simple-gamesystem-editor>

View File

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

View File

@ -0,0 +1,33 @@
import {Component, Input} from '@angular/core';
import {GameModel} from "../../game-model/GameModel";
import {Gamesystem} from "../../game-model/gamesystems/Gamesystem";
import {State} from "../../game-model/gamesystems/State";
import {Transition} from "../../game-model/gamesystems/Transition";
import {SimpleGamesystem} from "../../game-model/gamesystems/SimpleGamesystem";
import {ProductGamesystem} from "../../game-model/gamesystems/ProductGamesystem";
@Component({
selector: 'app-gamesystem-editor',
templateUrl: './gamesystem-editor.component.html',
styleUrl: './gamesystem-editor.component.scss'
})
export class GamesystemEditorComponent {
@Input() gamesystem: Gamesystem<State<any>, Transition<any>> | undefined
isSimpleGamesystem() {
return this.gamesystem instanceof SimpleGamesystem;
}
convertGamesystemToSimpleGamesystem() {
if(this.gamesystem instanceof SimpleGamesystem) {
return this.gamesystem as SimpleGamesystem;
}
}
convertGamesystemToProductGamesystem() {
if(this.gamesystem instanceof ProductGamesystem) {
return this.gamesystem as ProductGamesystem;
}
}
}

View File

@ -0,0 +1,2 @@
<app-simple-state-editor [states]="simpleGamesystem!.states" [gamesystem]="simpleGamesystem"></app-simple-state-editor>
<app-simple-transition-editor class="transition-editor" [gamesystem]="simpleGamesystem"></app-simple-transition-editor>

View File

@ -0,0 +1,3 @@
.transition-editor {
margin-top: 15px;
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleGamesystemEditorComponent } from './simple-gamesystem-editor.component';
describe('SimpleGamesystemEditorComponent', () => {
let component: SimpleGamesystemEditorComponent;
let fixture: ComponentFixture<SimpleGamesystemEditorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SimpleGamesystemEditorComponent]
})
.compileComponents();
fixture = TestBed.createComponent(SimpleGamesystemEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,16 @@
import {Component, Input} from '@angular/core';
import {SimpleGamesystem} from "../../../game-model/gamesystems/SimpleGamesystem";
import {MatTableDataSource} from "@angular/material/table";
@Component({
selector: 'app-simple-gamesystem-editor',
templateUrl: './simple-gamesystem-editor.component.html',
styleUrl: './simple-gamesystem-editor.component.scss'
})
export class SimpleGamesystemEditorComponent {
@Input() simpleGamesystem: SimpleGamesystem | undefined
}

View File

@ -0,0 +1,77 @@
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" multiTemplateDataRows>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Label</th>
<td mat-cell *matCellDef="let state">
<span *ngIf="editedElement !== state"> {{state.stateLabel}}</span>
<mat-form-field appearance="fill" class="long-form" *ngIf="editedElement === state">
<input matInput [(ngModel)]="state.stateLabel" (ngModelChange)="onStateChange()">
<mat-hint class="mat-error" *ngIf="editedStateLabelError"><mat-icon class="warning-icon">warning</mat-icon>Missing State-Label Information</mat-hint>
</mat-form-field>
</td>
</ng-container>
<!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
<ng-container matColumnDef="expandedDetail">
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplayWithExpand.length">
<div class="example-element-detail" [@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'">
<p *ngIf="editedElement !== element">{{element.stateDescription}}</p>
<mat-form-field appearance="fill" class="long-form" *ngIf="element === editedElement">
<mat-label>Description</mat-label>
<textarea matInput [(ngModel)]="element.stateDescription" rows="3" (ngModelChange)="onStateChange()"></textarea>
</mat-form-field>
</div>
</td>
</ng-container>
<ng-container matColumnDef="initial">
<th mat-header-cell *matHeaderCellDef>Initial</th>
<td mat-cell *matCellDef="let state">
<div *ngIf="editedElement !== state">
<mat-icon *ngIf="state.initial">done</mat-icon>
<mat-icon *ngIf="!state.initial">close</mat-icon>
</div>
<mat-checkbox *ngIf="editedElement === state" [(ngModel)]="state.initial" (ngModelChange)="onStateChange()"></mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="edit">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let state">
<button mat-icon-button color="primary" *ngIf="editedElement !== state" (click)="editState(state)" [disabled]="editedElement !== null"><mat-icon>edit</mat-icon></button>
<button mat-icon-button color="primary" (click)="finishEditing()" *ngIf="editedElement === state"><mat-icon>done</mat-icon></button>
</td>
</ng-container>
<ng-container matColumnDef="delete">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let state">
<button mat-icon-button color="warn" (click)="deleteState(state)"><mat-icon>delete</mat-icon></button>
</td>
</ng-container>
<ng-container matColumnDef="expand">
<th mat-header-cell *matHeaderCellDef aria-label="row actions">
<button mat-icon-button (click)="addState()"><mat-icon>add</mat-icon></button>
</th>
<td mat-cell *matCellDef="let element">
<button mat-icon-button aria-label="expand row" (click)="(expandedElement = expandedElement === element ? null : element); $event.stopPropagation()">
@if (expandedElement === element) {
<mat-icon>keyboard_arrow_up</mat-icon>
} @else {
<mat-icon>keyboard_arrow_down</mat-icon>
}
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplayWithExpand"></tr>
<tr mat-row *matRowDef="let element; columns: columnsToDisplayWithExpand;"
class="example-element-row"
[class.example-expanded-row]="expandedElement === element"
(click)="expandedElement = expandedElement === element ? null : element">
</tr>
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
</table>

View File

@ -0,0 +1,63 @@
table {
width: 100%;
}
tr.example-detail-row {
height: 0;
}
tr.example-element-row:not(.example-expanded-row):hover {
background: #545456
}
tr.example-element-row:not(.example-expanded-row):active {
background: #545456;
}
.example-element-row td {
border-bottom-width: 0;
}
.example-element-detail {
overflow: hidden;
display: flex;
}
.example-element-diagram {
min-width: 80px;
border: 2px solid black;
padding: 8px;
font-weight: lighter;
margin: 8px 0;
height: 104px;
}
.example-element-symbol {
font-weight: bold;
font-size: 40px;
line-height: normal;
}
.example-element-description {
padding: 16px;
}
.example-element-description-attribution {
opacity: 0.5;
}
.mat-column-edit, .mat-column-delete, .mat-column-expand {
width: 32px;
}
.long-form {
width: 100%;
}
.mat-error {
color: red;
}
.warning-icon {
margin-right: 5px;
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleStateEditorComponent } from './simple-state-editor.component';
describe('SimpleStateEditorComponent', () => {
let component: SimpleStateEditorComponent;
let fixture: ComponentFixture<SimpleStateEditorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SimpleStateEditorComponent]
})
.compileComponents();
fixture = TestBed.createComponent(SimpleStateEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,86 @@
import {Component, Input, OnInit} from '@angular/core';
import {SimpleState} from "../../../../game-model/gamesystems/SimpleState";
import {MatTableDataSource} from "@angular/material/table";
import {animate, state, style, transition, trigger} from "@angular/animations";
import {MatSnackBar} from "@angular/material/snack-bar";
import {SimpleGamesystem} from "../../../../game-model/gamesystems/SimpleGamesystem";
@Component({
selector: 'app-simple-state-editor',
templateUrl: './simple-state-editor.component.html',
styleUrl: './simple-state-editor.component.scss',
animations: [
trigger('detailExpand', [
state('collapsed,void', style({height: '0px', minHeight: '0'})),
state('expanded', style({height: '*'})),
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
]),
],
})
export class SimpleStateEditorComponent implements OnInit{
@Input() states: SimpleState[] = [];
@Input() gamesystem: SimpleGamesystem | undefined
dataSource = new MatTableDataSource();
displayedColumns = ["name", "initial", "edit", "delete"];
columnsToDisplayWithExpand = [...this.displayedColumns, 'expand'];
expandedElement: SimpleState | null = null;
editedElement: SimpleState | null = null;
editedStateLabelError: boolean = false;
constructor(private snackbar: MatSnackBar) {
}
ngOnInit() {
this.dataSource.data = this.states;
}
editState(state: SimpleState) {
if(this.editedElement === state) {
this.editedElement = null;
} else {
this.editedElement = state;
}
}
finishEditing() {
if(this.isEditedStateValid()) {
this.editedElement = null;
}
}
isEditedStateValid(): boolean {
if(this.editedElement!.stateLabel.length > 0) {
this.editedStateLabelError = false;
return true;
} else {
this.editedStateLabelError = true;
}
return false;
}
deleteState(state: SimpleState) {
if(this.gamesystem == undefined || this.gamesystem.parentGamesystem == undefined) {
this.gamesystem?.removeState(state);
this.dataSource.data = this.gamesystem!.states
} else {
}
}
addState() {
if(this.gamesystem != undefined) {
const simpleState = this.gamesystem.createState("New State", "");
if(simpleState != undefined) {
this.dataSource.data = this.gamesystem.states;
this.editedElement = simpleState;
this.expandedElement = simpleState;
this.onStateChange();
}
}
}
onStateChange() {
this.gamesystem!.onModifyContent();
}
}

View File

@ -0,0 +1,81 @@
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" multiTemplateDataRows>
<ng-container matColumnDef="starting-state">
<th mat-header-cell *matHeaderCellDef>Starting State</th>
<td mat-cell *matCellDef="let transition">
<span *ngIf="editedTransition !== transition">{{transition.startingState.stateLabel}}</span>
<mat-form-field appearance="fill" class="long-form" *ngIf="editedTransition == transition">
<mat-select [(ngModel)]="editedTransition!.startingState" (ngModelChange)="validateEditedTransition()">
<mat-option *ngFor="let state of gamesystem!.states" [value]="state"
(onSelectionChange)="validateEditedTransition()">{{state.stateLabel}}</mat-option>
</mat-select>
<mat-hint class="mat-error" *ngIf="transitionError"><mat-icon class="warning-icon">warning</mat-icon> Starting and Ending State cannot be the same!</mat-hint>
<mat-hint class="mat-error" *ngIf="!transitionError && transitionStartingStateError"><mat-icon class="warning-icon">warning</mat-icon> Select a valid Starting State!</mat-hint>
</mat-form-field>
</td>
</ng-container>
<ng-container matColumnDef="ending-state">
<th mat-header-cell *matHeaderCellDef>Ending State</th>
<td mat-cell *matCellDef="let transition">
<span *ngIf="editedTransition !== transition">{{transition.endingState.stateLabel}}</span>
<mat-form-field appearance="fill" class="long-form" *ngIf="editedTransition == transition">
<mat-select [(ngModel)]="editedTransition!.endingState" (ngModelChange)="validateEditedTransition()">
<mat-option *ngFor="let state of gamesystem!.states" [value]="state"
(onSelectionChange)="validateEditedTransition()">{{state.stateLabel}}</mat-option>
</mat-select>
<mat-hint class="mat-error" *ngIf="transitionError"><mat-icon class="warning-icon">warning</mat-icon> Starting and Ending State cannot be the same!</mat-hint>
<mat-hint class="mat-error" *ngIf="!transitionError && transitionEndingStateError"> <mat-icon class="warning-icon">warning</mat-icon> Select a valid Ending State!</mat-hint>
</mat-form-field>
</td>
</ng-container>
<ng-container matColumnDef="expandedDetail">
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplayWithExpand.length">
<div class="example-element-detail"
[@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'">
<p>Expanded Detail</p>
</div>
</td>
</ng-container>
<ng-container matColumnDef="edit">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let transition">
<button mat-icon-button color="primary" *ngIf="editedTransition !== transition" [disabled]="editedTransition != undefined"><mat-icon>edit</mat-icon></button>
<button mat-icon-button color="primary" *ngIf="editedTransition === transition"
[disabled]="transitionError || transitionStartingStateError || transitionEndingStateError" (click)="finishEditing()"
><mat-icon>done</mat-icon></button>
</td>
</ng-container>
<ng-container matColumnDef="delete">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let transition">
<button mat-icon-button color="warn" (click)="deleteTransition(transition)"><mat-icon>delete</mat-icon></button>
</td>
</ng-container>
<ng-container matColumnDef="expand">
<th mat-header-cell *matHeaderCellDef aria-label="row actions">
<button mat-icon-button (click)="addTransition()" [disabled]="editedTransition != undefined"><mat-icon>add</mat-icon></button>
</th>
<td mat-cell *matCellDef="let element">
<button mat-icon-button aria-label="expand row" (click)="(expandedElement = expandedElement === element ? null : element); $event.stopPropagation()">
@if (expandedElement === element) {
<mat-icon>keyboard_arrow_up</mat-icon>
} @else {
<mat-icon>keyboard_arrow_down</mat-icon>
}
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplayWithExpand"></tr>
<tr mat-row *matRowDef="let element; columns: columnsToDisplayWithExpand;"
class="example-element-row"
[class.example-expanded-row]="expandedElement === element"
(click)="expandedElement = expandedElement === element ? null : element">
</tr>
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
</table>

View File

@ -0,0 +1,63 @@
table {
width: 100%;
margin-top: 20px;
}
.mat-column-edit, .mat-column-delete, .mat-column-expand {
width: 32px;
}
tr.example-detail-row {
height: 0;
}
tr.example-element-row:not(.example-expanded-row):hover {
background: #545456;
}
tr.example-element-row:not(.example-expanded-row):active {
background: #545456;
}
.example-element-row td {
border-bottom-width: 0;
}
.example-element-detail {
overflow: hidden;
display: flex;
}
.example-element-diagram {
min-width: 80px;
border: 2px solid black;
padding: 8px;
font-weight: lighter;
margin: 8px 0;
height: 104px;
}
.example-element-symbol {
font-weight: bold;
font-size: 40px;
line-height: normal;
}
.example-element-description {
padding: 16px;
}
.example-element-description-attribution {
opacity: 0.5;
}
.long-form {
width: 100%;
}
.warning-icon {
margin-right: 5px;
}
.mat-error {
color: red;
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleTransitionEditorComponent } from './simple-transition-editor.component';
describe('SimpleTransitionEditorComponent', () => {
let component: SimpleTransitionEditorComponent;
let fixture: ComponentFixture<SimpleTransitionEditorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SimpleTransitionEditorComponent]
})
.compileComponents();
fixture = TestBed.createComponent(SimpleTransitionEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,86 @@
import {Component, Input, OnInit} from '@angular/core';
import {GameModel} from "../../../../game-model/GameModel";
import {SimpleGamesystem} from "../../../../game-model/gamesystems/SimpleGamesystem";
import {
MatCell,
MatCellDef,
MatColumnDef,
MatHeaderCell,
MatHeaderCellDef, MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef,
MatTable,
MatTableDataSource
} from "@angular/material/table";
import {SimpleTransition} from "../../../../game-model/gamesystems/SimpleTransition";
import {animate, state, style, transition, trigger} from "@angular/animations";
import {SimpleState} from "../../../../game-model/gamesystems/SimpleState";
@Component({
selector: 'app-simple-transition-editor',
templateUrl: './simple-transition-editor.component.html',
styleUrl: './simple-transition-editor.component.scss',
animations: [
trigger('detailExpand', [
state('collapsed,void', style({height: '0px', minHeight: '0'})),
state('expanded', style({height: '*'})),
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
]),
],
})
export class SimpleTransitionEditorComponent implements OnInit {
@Input() gamesystem: SimpleGamesystem | undefined
displayedColumns: string[] = ["starting-state", "ending-state", "edit", "delete"];
dataSource = new MatTableDataSource<SimpleTransition>();
columnsToDisplayWithExpand = [...this.displayedColumns, 'expand'];
expandedElement: SimpleTransition | null = null;
editedTransition: SimpleTransition | undefined
editedTransitionIndex = -1;
defaultStartingState: SimpleState = new SimpleState("None", "");
defaultEndingState: SimpleState = new SimpleState("None", "");
transitionError: boolean = false;
transitionStartingStateError = true;
transitionEndingStateError = true;
ngOnInit() {
this.dataSource.data = this.gamesystem!.transitions;
}
addTransition() {
this.editedTransition = new SimpleTransition(this.defaultStartingState, this.defaultEndingState);
const updatedData = this.dataSource.data;
updatedData.push(this.editedTransition);
this.dataSource.data = updatedData;
this.editedTransitionIndex = this.dataSource.data.length;
}
validateEditedTransition() {
this.transitionError = false;
this.transitionStartingStateError = false;
this.transitionEndingStateError = false;
if(this.editedTransition!.startingState === this.editedTransition!.endingState) {
this.transitionError = true;
} else if(this.editedTransition!.startingState == this.defaultStartingState) {
this.transitionStartingStateError = true;
} else if(this.editedTransition!.endingState == this.defaultEndingState) {
this.transitionEndingStateError = true;
}
}
finishEditing() {
this.validateEditedTransition();
if(this.transitionError || this.transitionStartingStateError || this.transitionEndingStateError) {
return;
}
this.gamesystem?.createTransition(this.editedTransition!.startingState, this.editedTransition!.endingState);
this.dataSource.data = this.gamesystem!.transitions;
this.editedTransition = undefined;
}
deleteTransition(transition: SimpleTransition) {
if(this.gamesystem!.parentGamesystem == undefined) {
this.gamesystem!.removeTransition(transition);
this.dataSource.data = this.gamesystem!.transitions;
}
}
}

View File

@ -1,4 +1,4 @@
import {Component, Input, OnInit} from '@angular/core';
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {ModelComponent} from "../../game-model/ModelComponent";
import {FormControl, Validators} from "@angular/forms";
@ -9,6 +9,7 @@ import {FormControl, Validators} from "@angular/forms";
})
export class ModelComponentEditorComponent implements OnInit{
@Input('modelComponent') modelComponent: ModelComponent | undefined
@Output("onModelNameUpdated") onModelNameUpdateEmitter = new EventEmitter<boolean>();
nameCtrl: FormControl = new FormControl('', [Validators.required]);
descriptionCtrl: FormControl = new FormControl('' );
@ -21,6 +22,7 @@ export class ModelComponentEditorComponent implements OnInit{
onUpdateName() {
this.modelComponent!.componentName = this.nameCtrl.value;
this.modelComponent!.onModifyContent();
this.onModelNameUpdateEmitter.emit(true);
}
onUpdateDescription() {

View File

@ -1,10 +1,14 @@
import {Gamesystem} from "./gamesystems/Gamesystem";
import {ScriptAccount} from "./scriptAccounts/ScriptAccount";
import {Transition} from "./gamesystems/Transition";
import {State} from "./gamesystems/State";
import {ProductGamesystem} from "./gamesystems/ProductGamesystem";
import {SimpleGamesystem} from "./gamesystems/SimpleGamesystem";
export class GameModel {
private readonly _gameModelName: string
private _gamesystems: Gamesystem[] = [];
private _gamesystems: Gamesystem<any, any>[] = [];
private _scriptAccounts: ScriptAccount[] = [];
constructor(gameModelName: string) {
@ -13,26 +17,25 @@ export class GameModel {
get gameModelName(): string {
return this._gameModelName;
}
get gamesystems(): Gamesystem[] {
get gamesystems(): Gamesystem<any, any>[] {
return this._gamesystems;
}
get scriptAccounts(): ScriptAccount[] {
return this._scriptAccounts;
}
addGamesystem(gamesystem: Gamesystem) {
if(!this.gamesystems.includes(gamesystem)) {
addGamesystem(gamesystem: Gamesystem<any, any>) {
if(this.findGamesystem(gamesystem.componentName) == undefined) {
this._gamesystems.push(gamesystem);
}
}
removeGamesystem(gamesystem : Gamesystem) {
removeGamesystem(gamesystem : Gamesystem<any, any>) {
this._gamesystems = this._gamesystems.filter(g => g !== gamesystem);
}
addScriptAccount(scriptAccountName: string) {
createScriptAccount(scriptAccountName: string) {
if(scriptAccountName != undefined && scriptAccountName.length > 0) {
const scriptAccount = new ScriptAccount(scriptAccountName, "");
const searchedScriptAccount = this.scriptAccounts.find(s => s.componentName === scriptAccount.componentName);
@ -44,10 +47,47 @@ export class GameModel {
return undefined;
}
createGamesystem(gamesystemName: string, parentGamesystemName: string | undefined) {
if(gamesystemName != undefined && this.findGamesystem(gamesystemName) == undefined) {
const simpleGamesystem = new SimpleGamesystem(gamesystemName);
if(parentGamesystemName != undefined) {
const parentGamesystem = this.findGamesystem(parentGamesystemName);
if(parentGamesystem instanceof SimpleGamesystem) {
const parentProductGamesystem = ProductGamesystem.constructFromSimpleGamesystem(parentGamesystem, this);
parentProductGamesystem.addChildGamesystem(simpleGamesystem);
simpleGamesystem.parentGamesystem = parentProductGamesystem;
} else {
const productParentGamesystem = parentGamesystem as ProductGamesystem;
productParentGamesystem.addChildGamesystem(simpleGamesystem);
simpleGamesystem.parentGamesystem = productParentGamesystem;
}
} else {
this.gamesystems.push(simpleGamesystem);
}
return simpleGamesystem;
}
}
removeScriptAccount(scriptAccount: ScriptAccount) {
if(scriptAccount != undefined) {
this._scriptAccounts = this.scriptAccounts.filter(s => s != scriptAccount);
}
}
findGamesystem(gamesystemName: string) {
const gamesystemQueue : Gamesystem<State<any>, Transition<any>>[] = [];
this.gamesystems.forEach(gamesystem => gamesystemQueue.push(gamesystem));
while(gamesystemQueue.length > 0) {
const currentGamesystem = gamesystemQueue.shift()!;
if(currentGamesystem.componentName == gamesystemName) {
return currentGamesystem;
} else if(currentGamesystem instanceof ProductGamesystem) {
const currentProductGamesystem = currentGamesystem as ProductGamesystem;
currentProductGamesystem.innerGamesystems.forEach(gamesystem => gamesystemQueue.push(gamesystem));
}
}
}
}

View File

@ -1,5 +1,5 @@
export enum ModelComponentType {
SCRIPTACCOUNT
SCRIPTACCOUNT,
GAMESYTEM
}

View File

@ -1,10 +1,10 @@
import {ModelComponentType} from "./ModelComponentType";
import {ModelComponent} from "./ModelComponent";
export class ModelComponentTypeUtillities {
static toString(modelComponentType: ModelComponentType | undefined): string {
switch (modelComponentType) {
case ModelComponentType.SCRIPTACCOUNT: return "ScriptAccounts";
case ModelComponentType.GAMESYTEM: return "Gamesystems";
default: return "Undefined";
}
}
@ -12,7 +12,15 @@ export class ModelComponentTypeUtillities {
static toSingleString(modelComponentType: ModelComponentType | undefined): string {
switch (modelComponentType) {
case ModelComponentType.SCRIPTACCOUNT: return "ScriptAccount";
case ModelComponentType.GAMESYTEM: return "Gamesystem";
default: return "Undefined";
}
}
static fromString(string: string) : ModelComponentType | undefined {
switch (string) {
case "gamesystem": return ModelComponentType.GAMESYTEM;
case "scriptaccount": return ModelComponentType.SCRIPTACCOUNT;
}
}
}

View File

@ -1,8 +1,35 @@
export class Gamesystem {
gamesystemName: string
import {SimpleGamesystem} from "./SimpleGamesystem";
import {ProductGamesystem} from "./ProductGamesystem";
import {ModelComponent} from "../ModelComponent";
import {ModelComponentType} from "../ModelComponentType";
export abstract class Gamesystem<S, T> extends ModelComponent{
states: S[] = [];
transitions: T[] = [];
constructor(gamesystemName: string) {
this.gamesystemName = gamesystemName;
super(gamesystemName, "", ModelComponentType.GAMESYTEM);
}
abstract createState(label: string, description: string): S|undefined;
abstract createTransition(startingState: S, endingState: S): T|undefined;
abstract removeState(state: S): boolean;
removeTransition(transition: T): boolean {
const updatedTransitions = this.transitions.filter(t => t !== transition);
if(updatedTransitions.length == this.transitions.length) {
return false;
}
this.transitions = updatedTransitions;
return true;
}
save() {
}
}

View File

@ -0,0 +1,60 @@
import {Gamesystem} from "./Gamesystem";
import {ProductState} from "./ProductState";
import {ProductTransition} from "./ProductTransition";
import {State} from "./State";
import {Transition} from "./Transition";
import {SimpleState} from "./SimpleState";
import {SimpleGamesystem} from "./SimpleGamesystem";
import {GameModel} from "../GameModel";
export class ProductGamesystem extends Gamesystem<ProductState, ProductTransition> {
innerGamesystems: Gamesystem<State<any>, Transition<any>>[] = [];
parentGamesystem: ProductGamesystem | undefined
static constructFromSimpleGamesystem(simpleGamesystem: SimpleGamesystem, gameModel: GameModel) {
const productGamesystem = new ProductGamesystem(simpleGamesystem.componentName);
const parentGamesystem = simpleGamesystem.parentGamesystem;
if(simpleGamesystem.states.length > 0) {
simpleGamesystem.componentName += "(Child)";
productGamesystem.addChildGamesystem(simpleGamesystem);
}
if(parentGamesystem != undefined) {
parentGamesystem.removeChildGamesystem(simpleGamesystem);
parentGamesystem.addChildGamesystem(productGamesystem);
} else {
gameModel.removeGamesystem(simpleGamesystem);
gameModel.addGamesystem(productGamesystem);
}
return productGamesystem;
}
createState(label: string, description: string): ProductState | undefined {
return undefined;
}
createTransition(startingState: ProductState, endingState: ProductState): ProductTransition | undefined {
return undefined;
}
removeState(state: ProductState): boolean {
return false;
}
removeInnerState(state: SimpleState) {
}
addChildGamesystem(gamesystem: Gamesystem<State<any>, Transition<any>>) {
this.innerGamesystems.push(gamesystem);
}
private removeChildGamesystem(gamesystem: Gamesystem<State<any>, Transition<any>>) {
this.innerGamesystems = this.innerGamesystems.filter(childSystem => childSystem != gamesystem);
}
}

View File

@ -0,0 +1,7 @@
import {ProductTransition} from "./ProductTransition";
import {State} from "./State";
import {SimpleState} from "./SimpleState";
export class ProductState extends State<ProductTransition> {
innerStates: SimpleState[] = [];
}

View File

@ -0,0 +1,6 @@
import {Transition} from "./Transition";
import {ProductState} from "./ProductState";
export class ProductTransition extends Transition<ProductState> {
}

View File

@ -0,0 +1,60 @@
import {Gamesystem} from "./Gamesystem";
import {SimpleState} from "./SimpleState";
import {SimpleTransition} from "./SimpleTransition";
import {State} from "./State";
import {Transition} from "./Transition";
import {ProductState} from "./ProductState";
import {ProductTransition} from "./ProductTransition";
import {ProductGamesystem} from "./ProductGamesystem";
export class SimpleGamesystem extends Gamesystem<SimpleState, SimpleTransition> {
parentGamesystem: ProductGamesystem | undefined
createState(label: string, description: string): SimpleState | undefined {
if(label == null) {
return undefined;
}
if(description == null) {
description = "";
}
const state = new SimpleState(label, description);
if(this.states.find(s => s.stateLabel == label) == undefined) {
this.states.push(state);
return state;
} else {
return undefined
}
}
createTransition(startingState: SimpleState, endingState: SimpleState): SimpleTransition | undefined{
if((startingState == null || endingState == null) || startingState === endingState) {
return undefined;
}
const transition = new SimpleTransition(startingState, endingState);
if(this.transitions.find(t => t.startingState === startingState && t.endingState === endingState) == undefined) {
this.transitions.push(transition)
return transition;
} else {
startingState.removeOutgoingTransition(transition);
endingState.removeIncomingTransition(transition);
return undefined
}
}
removeState(state: SimpleState): boolean {
const updatedStates = this.states.filter(s => s !== state);
const updated = updatedStates.length != this.states.length;
this.states = updatedStates;
this.transitions = this.transitions.filter(t => t.startingState !== state && t.endingState !== state);
return updated;
}
}

View File

@ -0,0 +1,6 @@
import {State} from "./State";
import {SimpleTransition} from "./SimpleTransition";
export class SimpleState extends State<SimpleTransition> {
}

View File

@ -0,0 +1,6 @@
import {SimpleState} from "./SimpleState";
import {Transition} from "./Transition";
export class SimpleTransition extends Transition<SimpleState> {
}

View File

@ -0,0 +1,32 @@
import {Transition} from "./Transition";
export abstract class State<T extends Transition<any>> {
stateLabel: string = "";
stateDescription: string = "";
incomingTransitions: T[] =[];
outgoingTransitions: T[] =[];
initial: boolean = false;
constructor(stateLabel: string, stateDescription: string) {
this.stateLabel = stateLabel;
this.stateDescription = stateDescription;
}
addIncomingTransition(transition: T) {
this.incomingTransitions.push(transition);
}
addOutgoingTransition(transition: T) {
this.outgoingTransitions.push(transition);
}
removeIncomingTransition(transition: T) {
}
removeOutgoingTransition(transition: T) {
}
}

View File

@ -0,0 +1,15 @@
import {State} from "./State";
export abstract class Transition<S extends State<any>> {
startingState: S
endingState: S
constructor(startingState: S, endingState: S) {
this.startingState = startingState;
this.endingState = endingState;
this.startingState.addOutgoingTransition(this);
this.endingState.addIncomingTransition(this);
}
}

View File

@ -0,0 +1,27 @@
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl" (contextmenu)="onContextMenu($event)">
<!-- This is the tree node template for leaf nodes -->
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding matTreeNodePaddingIndent="10" [ngClass]="selectedGamesystem === node ? 'selected-node':''"
(click)="onSelectGamesystem(node)" (contextmenu)="onSelectGamesystem(node)" (dblclick)="openGamesystemEditor(node)">
<!-- use a disabled button to provide padding for tree leaf -->
<button mat-icon-button class="small-icon-button" [disabled]="true"></button>
<div>
<button mat-icon-button class="small-icon-button"><mat-icon>code</mat-icon></button>
<span>{{node.name}}</span>
</div>
</mat-tree-node>
<!-- This is the tree node template for expandable nodes -->
<mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding matTreeNodePaddingIndent="10"
(click)="onSelectGamesystem(node)" (contextmenu)="onSelectGamesystem(node)" [ngClass]="selectedGamesystem === node ? 'selected-node':''"
(dblclick)="openGamesystemEditor(node)">
<button mat-icon-button matTreeNodeToggle
[attr.aria-label]="'Toggle ' + node.name" class="small-icon-button">
<mat-icon class="mat-icon-rtl-mirror">
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
</mat-icon>
</button>
<div>
<button mat-icon-button class="small-icon-button"><mat-icon>code</mat-icon></button>
<span>{{node.name}}</span>
</div>
</mat-tree-node>
</mat-tree>

View File

@ -0,0 +1,40 @@
.mat-tree-node {
min-height: 1.8em !important;
height: 1.8em;
}
.small-icon-button {
width: 26px !important;
height: 26px !important;
padding: 0px !important;
display: inline-flex !important;
align-items: center;
justify-content: center;
margin-left: 5px;
margin-bottom: 2px;
& > *[role=img] {
width: 18px;
height: 18px;
font-size: 18px;
svg {
width: 18px;
height: 18px;
}
}
.mat-mdc-button-touch-target {
width: 22px !important;
height: 22px !important;
}
}
.small-icon-button mat-icon {
color: whitesmoke;
}
.selected-node {
background-color: #545456
}

View File

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

View File

@ -0,0 +1,106 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {Gamesystem} from "../../game-model/gamesystems/Gamesystem";
import {State} from "../../game-model/gamesystems/State";
import {Transition} from "../../game-model/gamesystems/Transition";
import {ProductGamesystem} from "../../game-model/gamesystems/ProductGamesystem";
import {FlatTreeControl} from "@angular/cdk/tree";
import {
MatTree,
MatTreeFlatDataSource,
MatTreeFlattener,
MatTreeNode, MatTreeNodeDef,
MatTreeNodePadding, MatTreeNodeToggle
} from "@angular/material/tree";
import {MatIcon} from "@angular/material/icon";
import {MatIconButton} from "@angular/material/button";
import {SimpleGamesystem} from "../../game-model/gamesystems/SimpleGamesystem";
import {GameModel} from "../../game-model/GameModel";
import {ElectronService} from "../../core/services";
interface FlatNode {
expandable: boolean,
name: string,
level: number
}
@Component({
selector: 'app-gamescript-overview',
templateUrl: './gamescript-overview.component.html',
styleUrl: './gamescript-overview.component.scss'
})
export class GamescriptOverviewComponent implements OnInit {
@Input('gameModel') gameModel: GameModel | undefined
@Output('openGamesystemEditor') openGamesystemEmitter : EventEmitter<Gamesystem<State<any>, Transition<any>>> = new EventEmitter<Gamesystem<State<any>, Transition<any>>>();
ngOnInit() {
this.dataSource.data = this.gameModel!.gamesystems;
}
private _transformer = (node: Gamesystem<State<any>, Transition<any>>, level: number) => {
return {
expandable: this.isProductGamesystem(node) && !!(node as ProductGamesystem).innerGamesystems && (node as ProductGamesystem).innerGamesystems.length > 0,
name: node.componentName,
level: level
}
}
treeControl = new FlatTreeControl<FlatNode>(
node => node.level,
node => node.expandable
)
treeFlattener = new MatTreeFlattener(
this._transformer,
node => node.level,
node => node.expandable,
node => this.isSimpleGamesystem(node)? []: (node as ProductGamesystem).innerGamesystems
);
dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
selectedGamesystem: FlatNode | undefined;
constructor(private electronService: ElectronService) {
}
hasChild = (_: number, node: FlatNode) => node.expandable;
isSimpleGamesystem(gamesystem: Gamesystem<State<any>, Transition<any>>) {
return gamesystem instanceof SimpleGamesystem;
}
isProductGamesystem(gamesystem: Gamesystem<State<any>, Transition<any>>) {
return gamesystem instanceof ProductGamesystem;
}
onSelectGamesystem(node: FlatNode) {
this.selectedGamesystem = node;
}
onContextMenu(event: MouseEvent) {
this.electronService.ipcRenderer.send('context-menu', {x: event.x, y: event.y});
}
openGamesystemEditor(node: FlatNode) {
const gamesystem: Gamesystem<State<any>, Transition<any>>| undefined= this.gameModel!.findGamesystem(node.name);
if(gamesystem != undefined) {
gamesystem.unsaved = false;
this.openGamesystemEmitter.emit(gamesystem);
}
}
get selectedGamesystemName() {
if(this.selectedGamesystem == undefined) {
return undefined
} else {
return this.selectedGamesystem!.name
}
}
refresh() {
this.dataSource.data = this.gameModel!.gamesystems;
}
}

View File

@ -14,17 +14,7 @@ export class ScriptAccountOverviewComponent {
selectedScriptAccount: ScriptAccount | undefined
constructor(private electronService: ElectronService,
private zone: NgZone) {
if(electronService.isElectron) {
this.electronService.ipcRenderer.on('context-menu', (event: any, message: string) => {
this.zone.run(() => {
if(message == "new-scriptaccount") {
this.onCreateNewScriptAccount()
}
})
})
}
constructor() {
}
onOpenScriptAccount(scriptAccount: ScriptAccount) {
@ -32,12 +22,6 @@ export class ScriptAccountOverviewComponent {
this.openScriptAccountEmitter.emit(scriptAccount);
}
onCreateNewScriptAccount() {
const scriptAccount = this.gameModel!.addScriptAccount("New ScriptAccount");
if(scriptAccount != undefined) {
this.openScriptAccountEmitter.emit(scriptAccount);
}
}
selectScriptAccount(scriptAccount: ScriptAccount) {