diff --git a/angular.json b/angular.json
index a750f75..d7425fa 100644
--- a/angular.json
+++ b/angular.json
@@ -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": {
diff --git a/app/main.ts b/app/main.ts
index 17c3f2a..7f9f75d 100644
--- a/app/main.ts
+++ b/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;
}
diff --git a/e2e/game-model/scriptAccounts/ScriptAccountTest.spec.ts b/e2e/game-model/scriptAccounts/ScriptAccountTest.spec.ts
new file mode 100644
index 0000000..c7eb65b
--- /dev/null
+++ b/e2e/game-model/scriptAccounts/ScriptAccountTest.spec.ts
@@ -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();
+ })
+
+});
diff --git a/src/app/app.component.css b/src/app/app.component.css
index 6920c8c..d830d21 100644
--- a/src/app/app.component.css
+++ b/src/app/app.component.css
@@ -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%;
+}
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 460cc78..2beb7f6 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1 +1,31 @@
-add
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index f7b3ba7..325cbac 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -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")
+ }
+
+ }
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 2d75f4f..c3edf21 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -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]
diff --git a/src/app/delete-confirmation-dialog/delete-confirmation-dialog.component.html b/src/app/delete-confirmation-dialog/delete-confirmation-dialog.component.html
new file mode 100644
index 0000000..33297a2
--- /dev/null
+++ b/src/app/delete-confirmation-dialog/delete-confirmation-dialog.component.html
@@ -0,0 +1,12 @@
+
+ Delete
+
+
+
+
Delete {{ModelComponentTypeUtillities.toSingleString(deleteModelComponent.type)}}"{{deleteModelComponent.componentName}}"?
+
+
+
+
+
+
diff --git a/src/app/delete-confirmation-dialog/delete-confirmation-dialog.component.scss b/src/app/delete-confirmation-dialog/delete-confirmation-dialog.component.scss
new file mode 100644
index 0000000..9a8eca7
--- /dev/null
+++ b/src/app/delete-confirmation-dialog/delete-confirmation-dialog.component.scss
@@ -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;
+}
diff --git a/src/app/delete-confirmation-dialog/delete-confirmation-dialog.component.spec.ts b/src/app/delete-confirmation-dialog/delete-confirmation-dialog.component.spec.ts
new file mode 100644
index 0000000..74c7c4a
--- /dev/null
+++ b/src/app/delete-confirmation-dialog/delete-confirmation-dialog.component.spec.ts
@@ -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;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [DeleteConfirmationDialogComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(DeleteConfirmationDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/delete-confirmation-dialog/delete-confirmation-dialog.component.ts b/src/app/delete-confirmation-dialog/delete-confirmation-dialog.component.ts
new file mode 100644
index 0000000..8eacb90
--- /dev/null
+++ b/src/app/delete-confirmation-dialog/delete-confirmation-dialog.component.ts
@@ -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,
+ @Inject(MAT_DIALOG_DATA) public deleteModelComponent: ModelComponent) {
+ }
+ protected readonly ModelComponentTypeUtillities = ModelComponentTypeUtillities;
+
+ cancel() {
+ this.dialogRef.close(false);
+ }
+
+ confirmDelete() {
+ this.dialogRef.close(true);
+ }
+}
diff --git a/src/app/editor/editor.component.html b/src/app/editor/editor.component.html
new file mode 100644
index 0000000..ef4276b
--- /dev/null
+++ b/src/app/editor/editor.component.html
@@ -0,0 +1,13 @@
+
+
+
+ inventory_2
+ {{modelComponent.componentName}}
+
+
+
+
+
+
+
diff --git a/src/app/editor/editor.component.scss b/src/app/editor/editor.component.scss
new file mode 100644
index 0000000..26818c0
--- /dev/null
+++ b/src/app/editor/editor.component.scss
@@ -0,0 +1,11 @@
+.unsaved {
+ color: red;
+}
+
+.saved {
+ color: #3c95f8;
+}
+
+.close-btn {
+ color: white
+}
diff --git a/src/app/editor/editor.component.spec.ts b/src/app/editor/editor.component.spec.ts
new file mode 100644
index 0000000..33d9525
--- /dev/null
+++ b/src/app/editor/editor.component.spec.ts
@@ -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;
+
+ 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);
+ }))
+});
diff --git a/src/app/editor/editor.component.ts b/src/app/editor/editor.component.ts
new file mode 100644
index 0000000..6b222fe
--- /dev/null
+++ b/src/app/editor/editor.component.ts
@@ -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;
+}
diff --git a/src/app/editor/model-component-editor/model-component-editor.component.html b/src/app/editor/model-component-editor/model-component-editor.component.html
new file mode 100644
index 0000000..a9b59a0
--- /dev/null
+++ b/src/app/editor/model-component-editor/model-component-editor.component.html
@@ -0,0 +1,9 @@
+
+ Name
+
+ Please enter a valid number!
+
+
+ Description
+
+
diff --git a/src/app/editor/model-component-editor/model-component-editor.component.scss b/src/app/editor/model-component-editor/model-component-editor.component.scss
new file mode 100644
index 0000000..4e80027
--- /dev/null
+++ b/src/app/editor/model-component-editor/model-component-editor.component.scss
@@ -0,0 +1,3 @@
+.long-form {
+ width: 100%;
+}
diff --git a/src/app/editor/model-component-editor/model-component-editor.component.spec.ts b/src/app/editor/model-component-editor/model-component-editor.component.spec.ts
new file mode 100644
index 0000000..5b6dadf
--- /dev/null
+++ b/src/app/editor/model-component-editor/model-component-editor.component.spec.ts
@@ -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;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ModelComponentEditorComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(ModelComponentEditorComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/editor/model-component-editor/model-component-editor.component.ts b/src/app/editor/model-component-editor/model-component-editor.component.ts
new file mode 100644
index 0000000..c3a4e67
--- /dev/null
+++ b/src/app/editor/model-component-editor/model-component-editor.component.ts
@@ -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;
+}
diff --git a/src/app/editor/script-account-editor/script-account-editor.component.html b/src/app/editor/script-account-editor/script-account-editor.component.html
new file mode 100644
index 0000000..2b36b86
--- /dev/null
+++ b/src/app/editor/script-account-editor/script-account-editor.component.html
@@ -0,0 +1,13 @@
+
+
+ MinValue
+
+ Please enter a valid number!
+
+
+
+ MaxValue
+
+ Please enter a valid number!
+
+
diff --git a/src/app/editor/script-account-editor/script-account-editor.component.scss b/src/app/editor/script-account-editor/script-account-editor.component.scss
new file mode 100644
index 0000000..b3a1960
--- /dev/null
+++ b/src/app/editor/script-account-editor/script-account-editor.component.scss
@@ -0,0 +1,7 @@
+.example-form {
+ width: 100%;
+}
+
+.example-full-width {
+ width: 100%;
+}
diff --git a/src/app/editor/script-account-editor/script-account-editor.component.spec.ts b/src/app/editor/script-account-editor/script-account-editor.component.spec.ts
new file mode 100644
index 0000000..076be6c
--- /dev/null
+++ b/src/app/editor/script-account-editor/script-account-editor.component.spec.ts
@@ -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;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ScriptAccountEditorComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(ScriptAccountEditorComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/editor/script-account-editor/script-account-editor.component.ts b/src/app/editor/script-account-editor/script-account-editor.component.ts
new file mode 100644
index 0000000..bdf6bc2
--- /dev/null
+++ b/src/app/editor/script-account-editor/script-account-editor.component.ts
@@ -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();
+ }
+}
diff --git a/src/app/game-model/GameModel.ts b/src/app/game-model/GameModel.ts
index 8bc7dbf..a061db3 100644
--- a/src/app/game-model/GameModel.ts
+++ b/src/app/game-model/GameModel.ts
@@ -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);
+ }
+ }
+
}
diff --git a/src/app/game-model/ModelComponent.ts b/src/app/game-model/ModelComponent.ts
new file mode 100644
index 0000000..0020292
--- /dev/null
+++ b/src/app/game-model/ModelComponent.ts
@@ -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;
+ }
+}
diff --git a/src/app/game-model/ModelComponentType.ts b/src/app/game-model/ModelComponentType.ts
new file mode 100644
index 0000000..5218558
--- /dev/null
+++ b/src/app/game-model/ModelComponentType.ts
@@ -0,0 +1,5 @@
+export enum ModelComponentType {
+ SCRIPTACCOUNT
+
+
+}
diff --git a/src/app/game-model/ModelComponentTypeUtillities.ts b/src/app/game-model/ModelComponentTypeUtillities.ts
new file mode 100644
index 0000000..c92fce9
--- /dev/null
+++ b/src/app/game-model/ModelComponentTypeUtillities.ts
@@ -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";
+ }
+ }
+}
diff --git a/src/app/game-model/SaveComponent.ts b/src/app/game-model/SaveComponent.ts
new file mode 100644
index 0000000..473f0c5
--- /dev/null
+++ b/src/app/game-model/SaveComponent.ts
@@ -0,0 +1,13 @@
+export abstract class SaveComponent {
+ unsaved: boolean = false;
+
+ onModifyContent() {
+ this.unsaved = true;
+ }
+
+ onSaveContent() {
+ this.unsaved = false;
+ }
+
+ abstract save(): void;
+}
diff --git a/src/app/game-model/scriptAccounts/ScriptAccount.ts b/src/app/game-model/scriptAccounts/ScriptAccount.ts
new file mode 100644
index 0000000..f47f3c7
--- /dev/null
+++ b/src/app/game-model/scriptAccounts/ScriptAccount.ts
@@ -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 {
+ }
+
+}
diff --git a/src/app/side-overviews/script-account-overview/script-account-overview.component.css b/src/app/side-overviews/script-account-overview/script-account-overview.component.css
new file mode 100644
index 0000000..0dde805
--- /dev/null
+++ b/src/app/side-overviews/script-account-overview/script-account-overview.component.css
@@ -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;
+}
diff --git a/src/app/side-overviews/script-account-overview/script-account-overview.component.html b/src/app/side-overviews/script-account-overview/script-account-overview.component.html
new file mode 100644
index 0000000..797174b
--- /dev/null
+++ b/src/app/side-overviews/script-account-overview/script-account-overview.component.html
@@ -0,0 +1,8 @@
+
+
+ inventory_2{{scriptAccount.componentName}}
+
+
diff --git a/src/app/side-overviews/script-account-overview/script-account-overview.component.spec.ts b/src/app/side-overviews/script-account-overview/script-account-overview.component.spec.ts
new file mode 100644
index 0000000..991dc2e
--- /dev/null
+++ b/src/app/side-overviews/script-account-overview/script-account-overview.component.spec.ts
@@ -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;
+
+ 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);
+ }))
+
+
+});
diff --git a/src/app/side-overviews/script-account-overview/script-account-overview.component.ts b/src/app/side-overviews/script-account-overview/script-account-overview.component.ts
new file mode 100644
index 0000000..f8621ee
--- /dev/null
+++ b/src/app/side-overviews/script-account-overview/script-account-overview.component.ts
@@ -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 = new EventEmitter();
+
+ 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;
+ }
+}
diff --git a/src/styles.css b/src/styles.css
new file mode 100644
index 0000000..2fd855a
--- /dev/null
+++ b/src/styles.css
@@ -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;
+}
diff --git a/src/styles.scss b/src/styles.scss
deleted file mode 100644
index 4c1c4b2..0000000
--- a/src/styles.scss
+++ /dev/null
@@ -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;
- }
- }
-}
-