issue-2-scriptAccounts #4
@ -30,13 +30,13 @@
 | 
			
		||||
            "main": "src/main.ts",
 | 
			
		||||
            "tsConfig": "src/tsconfig.app.json",
 | 
			
		||||
            "polyfills": "src/polyfills.ts",
 | 
			
		||||
            "inlineStyleLanguage": "scss",
 | 
			
		||||
            "inlineStyleLanguage": "css",
 | 
			
		||||
            "assets": [
 | 
			
		||||
              "src/favicon.ico",
 | 
			
		||||
              "src/assets"
 | 
			
		||||
            ],
 | 
			
		||||
            "styles": [
 | 
			
		||||
              "src/styles.scss"
 | 
			
		||||
              "src/styles.css"
 | 
			
		||||
            ],
 | 
			
		||||
            "scripts": [],
 | 
			
		||||
            "customWebpackConfig": {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										43
									
								
								app/main.ts
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								app/main.ts
									
									
									
									
									
								
							@ -1,4 +1,4 @@
 | 
			
		||||
import {app, BrowserWindow, screen} from 'electron';
 | 
			
		||||
import {app, BrowserWindow, screen, Menu, ipcMain} from 'electron';
 | 
			
		||||
import * as path from 'path';
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
 | 
			
		||||
@ -50,6 +50,47 @@ function createWindow(): BrowserWindow {
 | 
			
		||||
    win = null;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const contextMenuTemplate = [
 | 
			
		||||
    {
 | 
			
		||||
      label: 'New',
 | 
			
		||||
      submenu: [
 | 
			
		||||
        {
 | 
			
		||||
          label: "Gamesystem",
 | 
			
		||||
          click: () => {
 | 
			
		||||
            win!.webContents.send('context-menu', "new-location");
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          label: "ScriptAccount",
 | 
			
		||||
          click: () => {
 | 
			
		||||
            win!.webContents.send('context-menu', "new-scriptaccount");
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      label: 'Edit...',
 | 
			
		||||
      click: () => {
 | 
			
		||||
        win!.webContents.send('context-menu', "edit");
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      label: 'Delete...',
 | 
			
		||||
      click: () => {
 | 
			
		||||
        win!.webContents.send('context-menu', "delete");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  const contextMenu = Menu.buildFromTemplate(contextMenuTemplate);
 | 
			
		||||
 | 
			
		||||
  win.webContents.on('context-menu', (e, params) => {
 | 
			
		||||
    console.log("Electron: Context menu showing")
 | 
			
		||||
    contextMenu.popup({ window: win!, x: params.x, y: params.y });
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  return win;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										71
									
								
								e2e/game-model/scriptAccounts/ScriptAccountTest.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								e2e/game-model/scriptAccounts/ScriptAccountTest.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,71 @@
 | 
			
		||||
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";
 | 
			
		||||
 | 
			
		||||
test.describe('Test ScriptAccounts', () => {
 | 
			
		||||
 | 
			
		||||
  test("Test ScriptAccount Creation", async () => {
 | 
			
		||||
    const scriptAccunt = new ScriptAccount("ScriptAccount", "Description");
 | 
			
		||||
 | 
			
		||||
    expect(scriptAccunt.componentName).toEqual("ScriptAccount");
 | 
			
		||||
    expect(scriptAccunt.componentDescription).toEqual("Description");
 | 
			
		||||
    expect(scriptAccunt.type).toEqual(ModelComponentType.SCRIPTACCOUNT);
 | 
			
		||||
    expect(scriptAccunt.minValue).toEqual(0);
 | 
			
		||||
    expect(scriptAccunt.maxValue).toEqual(100);
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test("Test Adding ScriptAccounts", async () => {
 | 
			
		||||
    const gameModel: GameModel = new GameModel("GameModel");
 | 
			
		||||
 | 
			
		||||
    let scriptAccount =gameModel.addScriptAccount("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")
 | 
			
		||||
    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);
 | 
			
		||||
    expect(result).toBeUndefined();
 | 
			
		||||
    expect(gameModel.scriptAccounts.length).toEqual(1);
 | 
			
		||||
    result = gameModel.addScriptAccount(undefined);
 | 
			
		||||
    expect(result).toBeUndefined();
 | 
			
		||||
    expect(gameModel.scriptAccounts.length).toEqual(1);
 | 
			
		||||
    result = gameModel.addScriptAccount("");
 | 
			
		||||
    expect(result).toBeUndefined();
 | 
			
		||||
    expect(gameModel.scriptAccounts.length).toEqual(1);
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test("test Removing ScriptAccounts", async () => {
 | 
			
		||||
    const gameModel: GameModel = new GameModel("GameModel");
 | 
			
		||||
    let scriptAccount = new ScriptAccount("test", "")
 | 
			
		||||
 | 
			
		||||
    gameModel.removeScriptAccount(scriptAccount);
 | 
			
		||||
    expect(gameModel.scriptAccounts.length).toEqual(0);
 | 
			
		||||
 | 
			
		||||
    scriptAccount = gameModel.addScriptAccount("ScriptAccount");
 | 
			
		||||
    gameModel.removeScriptAccount(scriptAccount);
 | 
			
		||||
    expect(gameModel.scriptAccounts.length).toEqual(0);
 | 
			
		||||
 | 
			
		||||
    gameModel.removeScriptAccount(undefined);
 | 
			
		||||
    expect(gameModel.scriptAccounts.length).toEqual(0);
 | 
			
		||||
 | 
			
		||||
    gameModel.removeScriptAccount(null);
 | 
			
		||||
    expect(gameModel.scriptAccounts.length).toEqual(0);
 | 
			
		||||
 | 
			
		||||
    scriptAccount = gameModel.addScriptAccount(scriptAccount);
 | 
			
		||||
    let scriptAccount2 = gameModel.addScriptAccount("ScriptAccount 2");
 | 
			
		||||
 | 
			
		||||
    gameModel.removeScriptAccount(scriptAccount);
 | 
			
		||||
    expect(gameModel.scriptAccounts.length).toEqual(1);
 | 
			
		||||
    expect(gameModel.scriptAccounts.includes(scriptAccount2)).toBeTruthy();
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
@ -1,3 +1,103 @@
 | 
			
		||||
.background {
 | 
			
		||||
.full-height-container {
 | 
			
		||||
  height: 99vh;
 | 
			
		||||
  background-color: #2b2d30;
 | 
			
		||||
  width: 40px;
 | 
			
		||||
 | 
			
		||||
  border-right: #b9b9b9;
 | 
			
		||||
  border-right-style: solid;
 | 
			
		||||
  border-right-width: 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.small-icon-button {
 | 
			
		||||
  width: 28px !important;
 | 
			
		||||
  height: 28px !important;
 | 
			
		||||
  padding: 0px !important;
 | 
			
		||||
  display: inline-flex !important;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  margin-left: 5px;
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
 | 
			
		||||
  & > *[role=img] {
 | 
			
		||||
    width: 20px;
 | 
			
		||||
    height: 20px;
 | 
			
		||||
    font-size: 20px;
 | 
			
		||||
 | 
			
		||||
    svg {
 | 
			
		||||
      width: 20px;
 | 
			
		||||
      height: 20px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .mat-mdc-button-touch-target {
 | 
			
		||||
    width: 24px !important;
 | 
			
		||||
    height: 24px !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.small-icon-button mat-icon {
 | 
			
		||||
  color: whitesmoke;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.selected {
 | 
			
		||||
  background-color: #4e5157;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.example-container {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.example-sidenav-content {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.example-sidenav {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.container {
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  display: flex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Adjust the vertical alignment of the icon within the button */
 | 
			
		||||
.mat-button-wrapper {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  border: none;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
  padding-right: 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sidenav-header {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.close-sidenav-btn {
 | 
			
		||||
  margin-top: 5px;
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sidenav-header:hover + .close-sidenav-btn {
 | 
			
		||||
  display: block;
 | 
			
		||||
  color: red;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
app-editor {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.example-form {
 | 
			
		||||
  min-width: 150px;
 | 
			
		||||
  max-width: 500px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.example-full-width {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1 +1,31 @@
 | 
			
		||||
<mat-icon>add</mat-icon>
 | 
			
		||||
<div class="container">
 | 
			
		||||
  <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>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  <mat-drawer-container class="example-container" autosize>
 | 
			
		||||
    <mat-drawer #drawer class="example-sidenav" mode="side">
 | 
			
		||||
      <div class="sidenav-header">
 | 
			
		||||
        <button mat-button [matMenuTriggerFor]="contentMenu">
 | 
			
		||||
          <span class="mat-button-wrapper">{{ModelComponentTypeUtillities.toString(openContent)}}
 | 
			
		||||
            <mat-icon class="mat-icon">expand_more</mat-icon>
 | 
			
		||||
          </span>
 | 
			
		||||
        </button>
 | 
			
		||||
 | 
			
		||||
        <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>
 | 
			
		||||
        </mat-menu>
 | 
			
		||||
      </div>
 | 
			
		||||
      <app-script-account-overview #scriptAccountOverview [gameModel]="gameModel" (onOpenScriptAccount)="openModelComponent($event)"></app-script-account-overview>
 | 
			
		||||
    </mat-drawer>
 | 
			
		||||
 | 
			
		||||
    <div class="example-sidenav-content">
 | 
			
		||||
      <app-editor #editor></app-editor>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
  </mat-drawer-container>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,36 @@
 | 
			
		||||
import { Component } from '@angular/core';
 | 
			
		||||
import { ElectronService } from './core/services';
 | 
			
		||||
import { APP_CONFIG } from '../environments/environment';
 | 
			
		||||
import {Component, NgZone, OnInit, ViewChild} from '@angular/core';
 | 
			
		||||
import {ElectronService} from './core/services';
 | 
			
		||||
import {APP_CONFIG} from '../environments/environment';
 | 
			
		||||
import {ModelComponentType} from "./game-model/ModelComponentType";
 | 
			
		||||
import {MatDrawerContainer} from "@angular/material/sidenav";
 | 
			
		||||
import {ModelComponentTypeUtillities} from "./game-model/ModelComponentTypeUtillities";
 | 
			
		||||
import {GameModel} from "./game-model/GameModel";
 | 
			
		||||
import {EditorComponent} from "./editor/editor.component";
 | 
			
		||||
import {ModelComponent} from "./game-model/ModelComponent";
 | 
			
		||||
import {
 | 
			
		||||
  ScriptAccountOverviewComponent
 | 
			
		||||
} from "./side-overviews/script-account-overview/script-account-overview.component";
 | 
			
		||||
import {MatDialog} from "@angular/material/dialog";
 | 
			
		||||
import {DeleteConfirmationDialogComponent} from "./delete-confirmation-dialog/delete-confirmation-dialog.component";
 | 
			
		||||
import {ScriptAccount} from "./game-model/scriptAccounts/ScriptAccount";
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-root',
 | 
			
		||||
  templateUrl: './app.component.html',
 | 
			
		||||
  styleUrls: ['./app.component.css']
 | 
			
		||||
})
 | 
			
		||||
export class AppComponent {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private electronService: ElectronService,
 | 
			
		||||
export class AppComponent implements OnInit{
 | 
			
		||||
 | 
			
		||||
  openContent : ModelComponentType | undefined = undefined;
 | 
			
		||||
  @ViewChild('drawer') drawer: MatDrawerContainer|undefined
 | 
			
		||||
  @ViewChild('editor') editor: EditorComponent|undefined
 | 
			
		||||
  @ViewChild('scriptAccountOverview') scriptAccountOverview: ScriptAccountOverviewComponent | undefined
 | 
			
		||||
 | 
			
		||||
  gameModel: GameModel | undefined
 | 
			
		||||
 | 
			
		||||
  constructor(private electronService: ElectronService,
 | 
			
		||||
              private zone: NgZone,
 | 
			
		||||
              private dialog: MatDialog
 | 
			
		||||
  ) {
 | 
			
		||||
    console.log('APP_CONFIG', APP_CONFIG);
 | 
			
		||||
 | 
			
		||||
@ -18,8 +39,69 @@ export class AppComponent {
 | 
			
		||||
      console.log('Run in electron');
 | 
			
		||||
      console.log('Electron ipcRenderer', this.electronService.ipcRenderer);
 | 
			
		||||
      console.log('NodeJS childProcess', this.electronService.childProcess);
 | 
			
		||||
 | 
			
		||||
      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);
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    } else {
 | 
			
		||||
      console.log('Run in browser');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getSelectedModelComponent(): ModelComponent | undefined {
 | 
			
		||||
    if(this.openContent == ModelComponentType.SCRIPTACCOUNT) {
 | 
			
		||||
      if(this.scriptAccountOverview != undefined) {
 | 
			
		||||
        return this.scriptAccountOverview!.selectedScriptAccount;
 | 
			
		||||
      } else {
 | 
			
		||||
        console.log("[WARN] [App.component] ScriptAccountOverview is undefined")
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.gameModel = new GameModel("No More");
 | 
			
		||||
    this.gameModel.addScriptAccount("Temperature");
 | 
			
		||||
    this.gameModel.addScriptAccount("Luftfeuchtigkeit");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  openScriptAccountsOverview() {
 | 
			
		||||
    this.openContent = ModelComponentType.SCRIPTACCOUNT
 | 
			
		||||
    this.drawer!.open();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected readonly ModelComponentType = ModelComponentType;
 | 
			
		||||
 | 
			
		||||
  closeContentOverview() {
 | 
			
		||||
    this.drawer!.close();
 | 
			
		||||
    this.openContent = undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected readonly ModelComponentTypeUtillities = ModelComponentTypeUtillities;
 | 
			
		||||
 | 
			
		||||
  openModelComponent(modelComponent: ModelComponent) {
 | 
			
		||||
    if(this.editor != undefined) {
 | 
			
		||||
      this.editor.openGameModelComponent(modelComponent);
 | 
			
		||||
    } else {
 | 
			
		||||
      console.log("[WARN] [App.Component] Editor is undefined")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { BrowserModule } from '@angular/platform-browser';
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { FormsModule } from '@angular/forms';
 | 
			
		||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 | 
			
		||||
import { HttpClientModule, HttpClient } from '@angular/common/http';
 | 
			
		||||
 | 
			
		||||
// NG Translate
 | 
			
		||||
@ -12,12 +12,35 @@ import {CoreModule} from "./core/core.module";
 | 
			
		||||
import {SharedModule} from "./shared/shared.module";
 | 
			
		||||
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 {MatInput} from "@angular/material/input";
 | 
			
		||||
import {MatDrawer, MatDrawerContainer} from "@angular/material/sidenav";
 | 
			
		||||
import {MatMenu, MatMenuItem, MatMenuTrigger} from "@angular/material/menu";
 | 
			
		||||
import {
 | 
			
		||||
  ScriptAccountOverviewComponent
 | 
			
		||||
} from "./side-overviews/script-account-overview/script-account-overview.component";
 | 
			
		||||
import {MatActionList, MatListItem} from "@angular/material/list";
 | 
			
		||||
import {EditorComponent} from "./editor/editor.component";
 | 
			
		||||
import {MatTab, MatTabGroup, MatTabLabel} from "@angular/material/tabs";
 | 
			
		||||
import {ScriptAccountEditorComponent} from "./editor/script-account-editor/script-account-editor.component";
 | 
			
		||||
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";
 | 
			
		||||
 | 
			
		||||
// AoT requires an exported function for factories
 | 
			
		||||
const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader =>  new TranslateHttpLoader(http, './assets/i18n/', '.json');
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [AppComponent],
 | 
			
		||||
  declarations: [
 | 
			
		||||
    AppComponent,
 | 
			
		||||
    ScriptAccountOverviewComponent,
 | 
			
		||||
    EditorComponent,
 | 
			
		||||
    ScriptAccountEditorComponent,
 | 
			
		||||
    ModelComponentEditorComponent,
 | 
			
		||||
    DeleteConfirmationDialogComponent
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    BrowserModule,
 | 
			
		||||
    FormsModule,
 | 
			
		||||
@ -32,7 +55,30 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader =>  new Transl
 | 
			
		||||
      }
 | 
			
		||||
    }),
 | 
			
		||||
    BrowserAnimationsModule,
 | 
			
		||||
    MatIcon
 | 
			
		||||
    MatIcon,
 | 
			
		||||
    MatToolbar,
 | 
			
		||||
    MatButton,
 | 
			
		||||
    MatFormField,
 | 
			
		||||
    MatInput,
 | 
			
		||||
    MatDrawerContainer,
 | 
			
		||||
    MatDrawer,
 | 
			
		||||
    MatIconButton,
 | 
			
		||||
    MatMenuTrigger,
 | 
			
		||||
    MatMenu,
 | 
			
		||||
    MatMenuItem,
 | 
			
		||||
    MatListItem,
 | 
			
		||||
    MatActionList,
 | 
			
		||||
    MatTabGroup,
 | 
			
		||||
    MatTab,
 | 
			
		||||
    MatTabLabel,
 | 
			
		||||
    MatLabel,
 | 
			
		||||
    MatFormField,
 | 
			
		||||
    ReactiveFormsModule,
 | 
			
		||||
    MatError,
 | 
			
		||||
    MatDialogTitle,
 | 
			
		||||
    MatDialogContent,
 | 
			
		||||
    MatDialogActions,
 | 
			
		||||
    MatMiniFabButton,
 | 
			
		||||
  ],
 | 
			
		||||
  providers: [],
 | 
			
		||||
  bootstrap: [AppComponent]
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,12 @@
 | 
			
		||||
<h3 mat-dialog-title class="dialog-title">
 | 
			
		||||
  <span style="margin-top: 10px">Delete</span>
 | 
			
		||||
  <button style="margin-top: 10px" mat-mini-fab class="small-icon-button" (click)="cancel()"><mat-icon>close</mat-icon></button>
 | 
			
		||||
</h3>
 | 
			
		||||
<div mat-dialog-content>
 | 
			
		||||
  <p>Delete {{ModelComponentTypeUtillities.toSingleString(deleteModelComponent.type)}}"{{deleteModelComponent.componentName}}"?</p>
 | 
			
		||||
</div>
 | 
			
		||||
<div mat-dialog-actions align="end">
 | 
			
		||||
  <button mat-raised-button class="btn-primary" (click)="confirmDelete()">Ok</button>
 | 
			
		||||
  <button mat-raised-button class="btn-secondary" (click)="cancel()">Cancel</button>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,41 @@
 | 
			
		||||
.dialog-title {
 | 
			
		||||
  background-color: #272727;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  height: 50px !important;
 | 
			
		||||
  padding-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.small-icon-button {
 | 
			
		||||
  width: 28px !important;
 | 
			
		||||
  height: 28px !important;
 | 
			
		||||
  padding: 0px !important;
 | 
			
		||||
  display: inline-flex !important;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  margin-left: 10px;
 | 
			
		||||
  margin-bottom: 5px;
 | 
			
		||||
  background-color: black;
 | 
			
		||||
 | 
			
		||||
  & > *[role=img] {
 | 
			
		||||
    width: 20px;
 | 
			
		||||
    height: 20px;
 | 
			
		||||
    font-size: 20px;
 | 
			
		||||
 | 
			
		||||
    svg {
 | 
			
		||||
      width: 20px;
 | 
			
		||||
      height: 20px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .mat-mdc-button-touch-target {
 | 
			
		||||
    width: 24px !important;
 | 
			
		||||
    height: 24px !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.small-icon-button mat-icon {
 | 
			
		||||
  color: whitesmoke;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { DeleteConfirmationDialogComponent } from './delete-confirmation-dialog.component';
 | 
			
		||||
 | 
			
		||||
describe('DeleteConfirmationDialogComponent', () => {
 | 
			
		||||
  let component: DeleteConfirmationDialogComponent;
 | 
			
		||||
  let fixture: ComponentFixture<DeleteConfirmationDialogComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      imports: [DeleteConfirmationDialogComponent]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
    
 | 
			
		||||
    fixture = TestBed.createComponent(DeleteConfirmationDialogComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,25 @@
 | 
			
		||||
import {Component, Inject} from '@angular/core';
 | 
			
		||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
 | 
			
		||||
import {ModelComponentTypeUtillities} from "../game-model/ModelComponentTypeUtillities";
 | 
			
		||||
import {ModelComponent} from "../game-model/ModelComponent";
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-delete-confirmation-dialog',
 | 
			
		||||
  templateUrl: './delete-confirmation-dialog.component.html',
 | 
			
		||||
  styleUrl: './delete-confirmation-dialog.component.scss'
 | 
			
		||||
})
 | 
			
		||||
export class DeleteConfirmationDialogComponent {
 | 
			
		||||
 | 
			
		||||
  constructor(private dialogRef: MatDialogRef<DeleteConfirmationDialogComponent>,
 | 
			
		||||
              @Inject(MAT_DIALOG_DATA) public deleteModelComponent: ModelComponent) {
 | 
			
		||||
  }
 | 
			
		||||
  protected readonly ModelComponentTypeUtillities = ModelComponentTypeUtillities;
 | 
			
		||||
 | 
			
		||||
  cancel() {
 | 
			
		||||
    this.dialogRef.close(false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  confirmDelete() {
 | 
			
		||||
    this.dialogRef.close(true);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/app/editor/editor.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/app/editor/editor.component.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
<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>
 | 
			
		||||
      <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-script-account-editor *ngIf="modelComponent.type === ModelComponentType.SCRIPTACCOUNT"
 | 
			
		||||
                               [scriptAccount]="convertModelComponentToScriptAccount(modelComponent)"></app-script-account-editor>
 | 
			
		||||
 | 
			
		||||
  </mat-tab>
 | 
			
		||||
</mat-tab-group>
 | 
			
		||||
							
								
								
									
										11
									
								
								src/app/editor/editor.component.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/app/editor/editor.component.scss
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
.unsaved {
 | 
			
		||||
  color: red;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.saved {
 | 
			
		||||
  color: #3c95f8;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.close-btn {
 | 
			
		||||
  color: white
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								src/app/editor/editor.component.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/app/editor/editor.component.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
			
		||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { EditorComponent } from './editor.component';
 | 
			
		||||
import {ScriptAccount} from "../game-model/scriptAccounts/ScriptAccount";
 | 
			
		||||
import {TranslateModule} from "@ngx-translate/core";
 | 
			
		||||
import {RouterTestingModule} from "@angular/router/testing";
 | 
			
		||||
 | 
			
		||||
describe('EditorComponent', () => {
 | 
			
		||||
  let component: EditorComponent;
 | 
			
		||||
  let fixture: ComponentFixture<EditorComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(waitForAsync(() => {
 | 
			
		||||
    void TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [EditorComponent],
 | 
			
		||||
      imports: [TranslateModule.forRoot(), RouterTestingModule]
 | 
			
		||||
    }).compileComponents();
 | 
			
		||||
 | 
			
		||||
    fixture = TestBed.createComponent(EditorComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  it("Test open ScriptAccount", waitForAsync (() => {
 | 
			
		||||
    const scriptAccount = new ScriptAccount("ScriptAccount", "");
 | 
			
		||||
    component.openGameModelComponent(scriptAccount);
 | 
			
		||||
 | 
			
		||||
    expect(component.gameModelComponents.includes(scriptAccount));
 | 
			
		||||
    expect(component.gameModelComponents.length).toEqual(1);
 | 
			
		||||
 | 
			
		||||
    component.openGameModelComponent(scriptAccount);
 | 
			
		||||
    expect(component.gameModelComponents.length).toEqual(1);
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  it("Test close ScriptAccount", waitForAsync(() => {
 | 
			
		||||
    const scriptAccount = new ScriptAccount("ScriptAccount", "");
 | 
			
		||||
    const scriptAccount2 = new ScriptAccount("ScriptAccount 2", "");
 | 
			
		||||
    component.openGameModelComponent(scriptAccount);
 | 
			
		||||
 | 
			
		||||
    component.closeGameModelComponent(scriptAccount);
 | 
			
		||||
    expect(component.gameModelComponents.length).toEqual(0);
 | 
			
		||||
 | 
			
		||||
    component.openGameModelComponent(scriptAccount);
 | 
			
		||||
    component.openGameModelComponent(scriptAccount2);
 | 
			
		||||
    component.closeGameModelComponent(scriptAccount);
 | 
			
		||||
    expect(component.gameModelComponents.length).toEqual(1);
 | 
			
		||||
    expect(component.gameModelComponents.includes(scriptAccount2));
 | 
			
		||||
  }))
 | 
			
		||||
 | 
			
		||||
  it("Test convert ModelComponent to ScriptAccount", waitForAsync(() => {
 | 
			
		||||
    const modelComponent = new ScriptAccount("test", "");
 | 
			
		||||
    const scriptAccount = component.convertModelComponentToScriptAccount(modelComponent);
 | 
			
		||||
 | 
			
		||||
    expect(scriptAccount).toBeInstanceOf(ScriptAccount);
 | 
			
		||||
  }))
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										32
									
								
								src/app/editor/editor.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/app/editor/editor.component.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
import {Component, Input} 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";
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-editor',
 | 
			
		||||
  templateUrl: './editor.component.html',
 | 
			
		||||
  styleUrl: './editor.component.scss'
 | 
			
		||||
})
 | 
			
		||||
export class EditorComponent {
 | 
			
		||||
  gameModelComponents: ModelComponent[] = [];
 | 
			
		||||
 | 
			
		||||
  openGameModelComponent(gameModelComponent: ModelComponent) {
 | 
			
		||||
    if(!this.gameModelComponents.includes(gameModelComponent)) {
 | 
			
		||||
      this.gameModelComponents.push(gameModelComponent);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  closeGameModelComponent(gameModelComponent: ModelComponent) {
 | 
			
		||||
    this.gameModelComponents = this.gameModelComponents.filter(modelComponent => modelComponent !== gameModelComponent);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  convertModelComponentToScriptAccount(modelComponent: ModelComponent) {
 | 
			
		||||
    if(modelComponent instanceof ScriptAccount) {
 | 
			
		||||
      return modelComponent as ScriptAccount;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected readonly ModelComponentType = ModelComponentType;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,9 @@
 | 
			
		||||
<mat-form-field appearance="fill" class="long-form">
 | 
			
		||||
  <mat-label>Name</mat-label>
 | 
			
		||||
  <input matInput [formControl]="nameCtrl" (change)="onUpdateName()">
 | 
			
		||||
  <mat-error *ngIf="nameCtrl.hasError('required')">Please enter a valid number!</mat-error>
 | 
			
		||||
</mat-form-field>
 | 
			
		||||
<mat-form-field appearance="fill" class="long-form">
 | 
			
		||||
  <mat-label>Description</mat-label>
 | 
			
		||||
  <textarea matInput [formControl]="descriptionCtrl" (change)="onUpdateDescription()" rows="3"></textarea>
 | 
			
		||||
</mat-form-field>
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
.long-form {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { ModelComponentEditorComponent } from './model-component-editor.component';
 | 
			
		||||
 | 
			
		||||
describe('ModelComponentEditorComponent', () => {
 | 
			
		||||
  let component: ModelComponentEditorComponent;
 | 
			
		||||
  let fixture: ComponentFixture<ModelComponentEditorComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      imports: [ModelComponentEditorComponent]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
    
 | 
			
		||||
    fixture = TestBed.createComponent(ModelComponentEditorComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,32 @@
 | 
			
		||||
import {Component, Input, OnInit} from '@angular/core';
 | 
			
		||||
import {ModelComponent} from "../../game-model/ModelComponent";
 | 
			
		||||
import {FormControl, Validators} from "@angular/forms";
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-model-component-editor',
 | 
			
		||||
  templateUrl: './model-component-editor.component.html',
 | 
			
		||||
  styleUrl: './model-component-editor.component.scss'
 | 
			
		||||
})
 | 
			
		||||
export class ModelComponentEditorComponent implements OnInit{
 | 
			
		||||
  @Input('modelComponent') modelComponent: ModelComponent | undefined
 | 
			
		||||
 | 
			
		||||
  nameCtrl: FormControl = new FormControl('', [Validators.required]);
 | 
			
		||||
  descriptionCtrl: FormControl = new FormControl('' );
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.nameCtrl.setValue(this.modelComponent!.componentName);
 | 
			
		||||
    this.descriptionCtrl.setValue(this.modelComponent!.componentDescription);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onUpdateName() {
 | 
			
		||||
    this.modelComponent!.componentName = this.nameCtrl.value;
 | 
			
		||||
    this.modelComponent!.onModifyContent();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onUpdateDescription() {
 | 
			
		||||
    this.modelComponent!.componentDescription = this.descriptionCtrl.value;
 | 
			
		||||
    this.modelComponent!.onModifyContent();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected readonly name = name;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
<div *ngIf="scriptAccount != undefined">
 | 
			
		||||
  <mat-form-field class="example-full-width">
 | 
			
		||||
    <mat-label>MinValue</mat-label>
 | 
			
		||||
    <input matInput [formControl]="minCtrl" type="number" (keypress)="onKeyPress($event)" (change)="onUpdateMinValue()">
 | 
			
		||||
    <mat-error *ngIf="minCtrl.hasError('required')">Please enter a valid number!</mat-error>
 | 
			
		||||
  </mat-form-field>
 | 
			
		||||
 | 
			
		||||
  <mat-form-field class="example-full-width">
 | 
			
		||||
    <mat-label>MaxValue</mat-label>
 | 
			
		||||
    <input matInput type="number" [formControl]="maxCtrl" (keypress)="onKeyPress($event)" (change)="onUpdateMaxValue()">
 | 
			
		||||
    <mat-error *ngIf="maxCtrl.hasError('required')">Please enter a valid number!</mat-error>
 | 
			
		||||
  </mat-form-field>
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,7 @@
 | 
			
		||||
.example-form {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.example-full-width {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { ScriptAccountEditorComponent } from './script-account-editor.component';
 | 
			
		||||
 | 
			
		||||
describe('ScriptAccountEditorComponent', () => {
 | 
			
		||||
  let component: ScriptAccountEditorComponent;
 | 
			
		||||
  let fixture: ComponentFixture<ScriptAccountEditorComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      imports: [ScriptAccountEditorComponent]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
    
 | 
			
		||||
    fixture = TestBed.createComponent(ScriptAccountEditorComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,49 @@
 | 
			
		||||
import {Component, Input, OnInit} from '@angular/core';
 | 
			
		||||
import {ScriptAccount} from "../../game-model/scriptAccounts/ScriptAccount";
 | 
			
		||||
import {ModelComponent} from "../../game-model/ModelComponent";
 | 
			
		||||
import {MatFormField} from "@angular/material/form-field";
 | 
			
		||||
import {MatInput} from "@angular/material/input";
 | 
			
		||||
import {FormControl, FormGroupDirective, FormsModule, NgForm, Validators} from "@angular/forms";
 | 
			
		||||
import {NgIf} from "@angular/common";
 | 
			
		||||
import {ErrorStateMatcher} from "@angular/material/core";
 | 
			
		||||
 | 
			
		||||
export class MyErrorStateMatcher implements ErrorStateMatcher {
 | 
			
		||||
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
 | 
			
		||||
    const isSubmitted = form && form.submitted;
 | 
			
		||||
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-script-account-editor',
 | 
			
		||||
  templateUrl: './script-account-editor.component.html',
 | 
			
		||||
  styleUrl: './script-account-editor.component.scss'
 | 
			
		||||
})
 | 
			
		||||
export class ScriptAccountEditorComponent implements OnInit{
 | 
			
		||||
  @Input("scriptAccount") scriptAccount : ScriptAccount | undefined
 | 
			
		||||
 | 
			
		||||
  minCtrl: FormControl = new FormControl(0, [Validators.required, Validators.pattern('^[0-9]*$')]);
 | 
			
		||||
  maxCtrl: FormControl = new FormControl(100, [Validators.required, Validators.pattern('^[0-9]*$')]);
 | 
			
		||||
  matcher = new MyErrorStateMatcher();
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.minCtrl.setValue(this.scriptAccount!.minValue);
 | 
			
		||||
    this.maxCtrl.setValue(this.scriptAccount!.maxValue);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onKeyPress(event: KeyboardEvent) {
 | 
			
		||||
    const input = event.key;
 | 
			
		||||
    const isDigit = /^\d+$/.test(input);
 | 
			
		||||
    if (!isDigit) {
 | 
			
		||||
      event.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onUpdateMinValue() {
 | 
			
		||||
    this.scriptAccount!.minValue = Number(this.minCtrl.value);
 | 
			
		||||
    this.scriptAccount!.onModifyContent();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onUpdateMaxValue() {
 | 
			
		||||
    this.scriptAccount!.maxValue = Number(this.maxCtrl.value);
 | 
			
		||||
    this.scriptAccount!.onModifyContent();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,9 +1,11 @@
 | 
			
		||||
import {Gamesystem} from "./gamesystems/Gamesystem";
 | 
			
		||||
import {ScriptAccount} from "./scriptAccounts/ScriptAccount";
 | 
			
		||||
 | 
			
		||||
export class GameModel {
 | 
			
		||||
  private readonly _gameModelName: string
 | 
			
		||||
 | 
			
		||||
  private _gamesystems: Gamesystem[] = [];
 | 
			
		||||
  private _scriptAccounts: ScriptAccount[] = [];
 | 
			
		||||
 | 
			
		||||
  constructor(gameModelName: string) {
 | 
			
		||||
    this._gameModelName = gameModelName;
 | 
			
		||||
@ -16,6 +18,9 @@ export class GameModel {
 | 
			
		||||
  get gamesystems(): Gamesystem[] {
 | 
			
		||||
    return this._gamesystems;
 | 
			
		||||
  }
 | 
			
		||||
  get scriptAccounts(): ScriptAccount[] {
 | 
			
		||||
    return this._scriptAccounts;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addGamesystem(gamesystem: Gamesystem) {
 | 
			
		||||
    if(!this.gamesystems.includes(gamesystem)) {
 | 
			
		||||
@ -26,4 +31,23 @@ export class GameModel {
 | 
			
		||||
  removeGamesystem(gamesystem : Gamesystem) {
 | 
			
		||||
    this._gamesystems = this._gamesystems.filter(g => g !== gamesystem);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addScriptAccount(scriptAccountName: string) {
 | 
			
		||||
    if(scriptAccountName != undefined && scriptAccountName.length > 0) {
 | 
			
		||||
      const scriptAccount = new ScriptAccount(scriptAccountName, "");
 | 
			
		||||
      const searchedScriptAccount = this.scriptAccounts.find(s => s.componentName === scriptAccount.componentName);
 | 
			
		||||
      if(searchedScriptAccount == undefined) {
 | 
			
		||||
        this.scriptAccounts.push(scriptAccount);
 | 
			
		||||
        return scriptAccount;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  removeScriptAccount(scriptAccount: ScriptAccount) {
 | 
			
		||||
    if(scriptAccount != undefined) {
 | 
			
		||||
      this._scriptAccounts = this.scriptAccounts.filter(s => s != scriptAccount);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								src/app/game-model/ModelComponent.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/app/game-model/ModelComponent.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
import {ModelComponentType} from "./ModelComponentType";
 | 
			
		||||
import {SaveComponent} from "./SaveComponent";
 | 
			
		||||
 | 
			
		||||
export abstract class ModelComponent extends SaveComponent{
 | 
			
		||||
  componentName: string
 | 
			
		||||
  componentDescription: string
 | 
			
		||||
  type: ModelComponentType
 | 
			
		||||
  constructor(componentName: string, componentDescription: string, type: ModelComponentType) {
 | 
			
		||||
    super();
 | 
			
		||||
    this.componentName = componentName;
 | 
			
		||||
    this.componentDescription = componentDescription;
 | 
			
		||||
    this.type = type;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								src/app/game-model/ModelComponentType.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/app/game-model/ModelComponentType.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
export enum ModelComponentType {
 | 
			
		||||
  SCRIPTACCOUNT
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/app/game-model/ModelComponentTypeUtillities.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/app/game-model/ModelComponentTypeUtillities.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
import {ModelComponentType} from "./ModelComponentType";
 | 
			
		||||
import {ModelComponent} from "./ModelComponent";
 | 
			
		||||
 | 
			
		||||
export class ModelComponentTypeUtillities {
 | 
			
		||||
  static toString(modelComponentType: ModelComponentType | undefined): string {
 | 
			
		||||
    switch (modelComponentType) {
 | 
			
		||||
      case ModelComponentType.SCRIPTACCOUNT: return "ScriptAccounts";
 | 
			
		||||
      default: return "Undefined";
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static toSingleString(modelComponentType: ModelComponentType | undefined): string {
 | 
			
		||||
    switch (modelComponentType) {
 | 
			
		||||
      case ModelComponentType.SCRIPTACCOUNT: return "ScriptAccount";
 | 
			
		||||
      default: return "Undefined";
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/app/game-model/SaveComponent.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/app/game-model/SaveComponent.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
export abstract class SaveComponent {
 | 
			
		||||
  unsaved: boolean = false;
 | 
			
		||||
 | 
			
		||||
  onModifyContent() {
 | 
			
		||||
    this.unsaved = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onSaveContent() {
 | 
			
		||||
    this.unsaved = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  abstract save(): void;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								src/app/game-model/scriptAccounts/ScriptAccount.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/app/game-model/scriptAccounts/ScriptAccount.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
import {ModelComponent} from "../ModelComponent";
 | 
			
		||||
import {ModelComponentType} from "../ModelComponentType";
 | 
			
		||||
 | 
			
		||||
export class ScriptAccount extends ModelComponent{
 | 
			
		||||
  minValue: number = 0;
 | 
			
		||||
  maxValue: number = 100;
 | 
			
		||||
  constructor(componentName: string, componentDescription: string) {
 | 
			
		||||
    super(componentName, componentDescription, ModelComponentType.SCRIPTACCOUNT);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  save(): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,14 @@
 | 
			
		||||
.scriptAccount-item {
 | 
			
		||||
  min-height: 1.8em !important;
 | 
			
		||||
  height: 1.8em !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.scriptAccount-icon {
 | 
			
		||||
  margin-right: 10px;
 | 
			
		||||
  color: #ccffff;
 | 
			
		||||
  align-content: baseline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.selected-item {
 | 
			
		||||
  background-color: #8696b6;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,8 @@
 | 
			
		||||
<mat-action-list>
 | 
			
		||||
  <mat-list-item class="scriptAccount-item" *ngFor="let scriptAccount of gameModel!.scriptAccounts"
 | 
			
		||||
                 (dblclick)="onOpenScriptAccount(scriptAccount)" (click)="selectScriptAccount(scriptAccount)"
 | 
			
		||||
                  [ngClass]="selectedScriptAccount === scriptAccount ?'selected-item':''"
 | 
			
		||||
                  (contextmenu)="selectScriptAccount(scriptAccount)">
 | 
			
		||||
    <mat-icon class="scriptAccount-icon">inventory_2</mat-icon>{{scriptAccount.componentName}}
 | 
			
		||||
  </mat-list-item>
 | 
			
		||||
</mat-action-list>
 | 
			
		||||
@ -0,0 +1,46 @@
 | 
			
		||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import {TranslateModule} from "@ngx-translate/core";
 | 
			
		||||
import {RouterTestingModule} from "@angular/router/testing";
 | 
			
		||||
import {ScriptAccountOverviewComponent} from "./script-account-overview.component";
 | 
			
		||||
import exp from "node:constants";
 | 
			
		||||
import {GameModel} from "../../game-model/GameModel";
 | 
			
		||||
 | 
			
		||||
describe('ScriptAccountOverview', () => {
 | 
			
		||||
  let component: ScriptAccountOverviewComponent;
 | 
			
		||||
  let fixture: ComponentFixture<ScriptAccountOverviewComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(waitForAsync(() => {
 | 
			
		||||
    void TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ScriptAccountOverviewComponent],
 | 
			
		||||
      imports: [TranslateModule.forRoot(), RouterTestingModule]
 | 
			
		||||
    }).compileComponents();
 | 
			
		||||
 | 
			
		||||
    fixture = TestBed.createComponent(ScriptAccountOverviewComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    component.gameModel = new GameModel("GameModel")
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("Test ScriptAccount Creation", waitForAsync(() => {
 | 
			
		||||
    component.onCreateNewScriptAccount();
 | 
			
		||||
    expect(component.gameModel!.scriptAccounts.length).toEqual(1)
 | 
			
		||||
    component.gameModel!.removeScriptAccount(component.gameModel!.scriptAccounts[0]);
 | 
			
		||||
 | 
			
		||||
    jest.spyOn(component.openScriptAccountEmitter, 'emit');
 | 
			
		||||
    component.onCreateNewScriptAccount();
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
 | 
			
		||||
    expect(component.openScriptAccountEmitter.emit).toHaveBeenCalledWith(component.gameModel!.scriptAccounts[0]);
 | 
			
		||||
 | 
			
		||||
    component.onCreateNewScriptAccount();
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
    expect(component.openScriptAccountEmitter.emit).toBeCalledTimes(1);
 | 
			
		||||
  }))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,46 @@
 | 
			
		||||
import {Component, EventEmitter, Input, NgZone, Output} from '@angular/core';
 | 
			
		||||
import {GameModel} from "../../game-model/GameModel";
 | 
			
		||||
import {ScriptAccount} from "../../game-model/scriptAccounts/ScriptAccount";
 | 
			
		||||
import {ElectronService} from "../../core/services";
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-script-account-overview',
 | 
			
		||||
  templateUrl: './script-account-overview.component.html',
 | 
			
		||||
  styleUrl: './script-account-overview.component.css'
 | 
			
		||||
})
 | 
			
		||||
export class ScriptAccountOverviewComponent {
 | 
			
		||||
  @Input("gameModel") gameModel: GameModel | undefined
 | 
			
		||||
  @Output("onOpenScriptAccount") openScriptAccountEmitter: EventEmitter<ScriptAccount> = new EventEmitter<ScriptAccount>();
 | 
			
		||||
 | 
			
		||||
  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()
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onOpenScriptAccount(scriptAccount: ScriptAccount) {
 | 
			
		||||
    console.log("onOpenScriptAccount (overview)")
 | 
			
		||||
    this.openScriptAccountEmitter.emit(scriptAccount);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onCreateNewScriptAccount() {
 | 
			
		||||
    const scriptAccount = this.gameModel!.addScriptAccount("New ScriptAccount");
 | 
			
		||||
    if(scriptAccount != undefined) {
 | 
			
		||||
      this.openScriptAccountEmitter.emit(scriptAccount);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  selectScriptAccount(scriptAccount: ScriptAccount) {
 | 
			
		||||
    this.selectedScriptAccount = scriptAccount;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								src/styles.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/styles.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
/* You can add global styles to this file, and also import other style files */
 | 
			
		||||
@import "@angular/material/prebuilt-themes/purple-green.css";
 | 
			
		||||
body {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html, body { height: 100%; margin: 0 }
 | 
			
		||||
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
 | 
			
		||||
 | 
			
		||||
.btn-primary {
 | 
			
		||||
  background-color: #3c95f8 !important;
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-secondary {
 | 
			
		||||
  background-color: #4f5459 !important;
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
@ -1,50 +0,0 @@
 | 
			
		||||
/* You can add global styles to this file, and also import other style files */
 | 
			
		||||
html, body {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  font-family: Arial, Helvetica, sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* CAN (MUST) BE REMOVED ! Sample Global style */
 | 
			
		||||
.container {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
 | 
			
		||||
  background: url(./assets/background.jpg) no-repeat center fixed;
 | 
			
		||||
  -webkit-background-size: cover; /* pour anciens Chrome et Safari */
 | 
			
		||||
  background-size: cover; /* version standardisée */
 | 
			
		||||
 | 
			
		||||
  .title {
 | 
			
		||||
    color: white;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 50px 20px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  a {
 | 
			
		||||
    color: #fff !important;
 | 
			
		||||
    text-transform: uppercase;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    background: #ed3330;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    border: none;
 | 
			
		||||
    transition: all 0.4s ease 0s;
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      background: #fff;
 | 
			
		||||
      color: #ed3330 !important;
 | 
			
		||||
      letter-spacing: 1px;
 | 
			
		||||
      -webkit-box-shadow: 0px 5px 40px -10px rgba(0,0,0,0.57);
 | 
			
		||||
      -moz-box-shadow: 0px 5px 40px -10px rgba(0,0,0,0.57);
 | 
			
		||||
      box-shadow: 5px 40px -10px rgba(0,0,0,0.57);
 | 
			
		||||
      transition: all 0.4s ease 0s;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user