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