Parcourir la source

notes and npcs finished. styling and hovering for notes coming next

Warafear il y a 6 mois
Parent
commit
5d81880ecb

+ 6 - 1
src/app/character/character-creator/character-creator.component.ts

@@ -398,7 +398,12 @@ export class CharacterCreatorComponent {
       this.dataAccessor.addData(
         this.characterName,
         {
-          data: {},
+          data: {
+            companions: [],
+            allies: [],
+            enemies: [],
+            others: [],
+          },
         },
         'npcs',
       ),

+ 22 - 14
src/app/journal/journal-notes/journal-notes.component.html

@@ -1,4 +1,6 @@
 <div class="entries-list">
+  <div class="title t-0">{{ "notes.sessions" | translate }}</div>
+  <divider appearance="gold-2" style="padding: 0 1rem"></divider>
   <!-- Add Button or temporary unsaved new entry -->
   @if (isNewEntry) {
     <div class="entry active">
@@ -6,13 +8,13 @@
         @if (currentEntry.title !== "") {
           {{ currentEntry.title }}
         } @else {
-          Noch kein Titel
+          {{ "notes.noTitle" | translate }}
         }
       </div>
       <div class="entry-date">
         {{ currentEntry.created | date: "shortDate" : "" : "de" }}
       </div>
-      <div class="unsaved">Nicht gespeichert</div>
+      <div class="unsaved">{{ "notes.unsaved" | translate }}</div>
     </div>
   } @else {
     <div class="entry add-button" (click)="addEntry()">
@@ -33,14 +35,14 @@
           @if (entries[index].title !== "") {
             {{ entries[index].title }}
           } @else {
-            Kein Titel
+            {{ "notes.noTitle" | translate }}
           }
         </div>
         <div class="entry-date">
           {{ entries[index].created | date: "shortDate" : "" : "de" }}
         </div>
         @if (isInEditMode && currentEntryIndex === index) {
-          <div class="unsaved">Nicht gespeichert</div>
+          <div class="unsaved">{{ "notes.unsaved" | translate }}</div>
         }
       </div>
       <div class="control-button-wrapper">
@@ -102,19 +104,25 @@
     }
     <div class="button-row">
       @if (isInEditMode) {
-        <ui-button width="w16" (click)="saveEntry()">Speichern</ui-button>
-        <ui-button width="w16" (click)="discardEntry()">Verwerfen</ui-button>
+        <ui-button width="w16" (click)="saveEntry()">{{
+          "notes.save" | translate
+        }}</ui-button>
+        <ui-button width="w16" (click)="discardEntry()">{{
+          "notes.discard" | translate
+        }}</ui-button>
       }
-      <!-- @else {
-        <ui-button width="w16" (click)="editEntry()">Bearbeiten</ui-button>
-        <ui-button width="w16" (click)="deleteEntry()"
-          >Eintrag löschen</ui-button
-        >
-      } -->
     </div>
   </div>
 } @else {
-  <div style="margin: 5rem auto; text-align: center">
-    Noch kein Eintrag vorhanden
+  <div
+    style="
+      margin: 28rem auto;
+      text-align: center;
+      font-size: 1.5rem;
+      font-weight: 600;
+      padding-left: 15rem;
+    "
+  >
+    {{ "notes.empty" | translate }}
   </div>
 }

+ 1 - 1
src/app/journal/journal-notes/journal-notes.component.scss

@@ -11,7 +11,7 @@
   background-image: url("../../../assets/images/texture-10.jpg");
   border-right: var(--gold-3);
   box-shadow: var(--shadow);
-  padding-top: 3rem;
+  padding-top: 2rem;
 
   .entry {
     width: 15rem;

+ 1 - 1
src/app/journal/journal-notes/journal-notes.component.ts

@@ -81,8 +81,8 @@ export class JournalNotesComponent implements OnInit, OnDestroy {
     };
     this.isNewEntry = true;
     this.isInEditMode = true;
-    // Hightlight no entry because the placeholder is shown as active
     this.backupIndex = this.currentEntryIndex;
+    // Hightlight no entry because the placeholder is shown as active
     this.currentEntryIndex = -1;
   }
 

+ 138 - 74
src/app/journal/journal-npcs/journal-npcs.component.html

@@ -1,82 +1,146 @@
 <div class="npcs-container">
-  <!-- ALLIES -->
-  <!-- <div class="npc-list-title">Verbündete</div>
-  <button class="toggle-button" (click)="collapse.toggle()">
-    @if (isAlliesCollapsed) {
-      Verbündete anzeigen
-    } @else {
-      Verbündete verbergen
-    }
-  </button>
-
-  <div
-    #collapse="ngbCollapse"
-    class="npc-list"
-    [(ngbCollapse)]="isAlliesCollapsed"
-  >
-    @if (isNewEntry) {
-
-    } @else {
-      <div class="npc-wrapper" (click)="addNpc('Ally')">
-        <div class="npc add-button">
-          <img src="assets/icons/UIIcons/add.svg" />
+  <div class="title t-0">{{ "npcs.characters" | translate }}</div>
+  <divider appearance="gold-2" style="width: 15rem"></divider>
+  <mat-accordion class="t-1" multi>
+    @for (group of ["companions", "allies", "enemies", "others"]; track group) {
+      <mat-expansion-panel [expanded]="true" class="b-1">
+        <mat-expansion-panel-header>
+          <mat-panel-title>
+            <div class="npc-list-title">{{ "npcs." + group | translate }}</div>
+          </mat-panel-title>
+        </mat-expansion-panel-header>
+        <divider [appearance]="'gold-2'" class="b-1"></divider>
+        <!-- Add-Button -->
+        @if (isNewNpc && currentType === group) {
+          <div class="npc active">
+            <div class="npc-title">
+              @if (currentNpc.name !== "") {
+                {{ currentNpc.name }}
+              } @else {
+                {{ "notes.noName" | translate }}
+              }
+            </div>
+            <div class="unsaved">{{ "notes.unsaved" | translate }}</div>
+          </div>
+        } @else {
+          <div class="npc-wrapper" (click)="addNpc(group)">
+            <div
+              class="npc add-button"
+              [ngClass]="
+                currentType === group && isInEditMode && currentIndex === -1
+                  ? 'active'
+                  : ''
+              "
+            >
+              <img src="assets/icons/UIIcons/add.svg" />
+            </div>
+          </div>
+        }
+        <div class="column gap-05 t-1">
+          @for (npc of npcs[group]; let index = $index; track npc) {
+            <div
+              class="npc-wrapper"
+              [ngClass]="{
+                active: currentIndex === index && currentType === group,
+                'edit-mode': isInEditMode
+              }"
+            >
+              <div class="npc" (click)="selectNpc(index, group)">
+                <div class="npc-title">{{ npc.name }}</div>
+              </div>
+              <div class="control-button-wrapper">
+                <icon-button
+                  [icon]="'edit-large'"
+                  (click)="editNpc()"
+                ></icon-button>
+                <icon-button
+                  [icon]="'delete'"
+                  (click)="deleteNpc()"
+                ></icon-button>
+              </div>
+            </div>
+          }
         </div>
-      </div>
+      </mat-expansion-panel>
     }
-    @for (npc of npcs.allies; track npc) {
-      <div class="npc-wrapper">
-        <div class="npc">
-          <div class="npc-title">{{ npc.name }}</div>
+  </mat-accordion>
+</div>
+@if (!npcsIsEmpty) {
+  @if (isInEditMode) {
+    <div class="write-container">
+      <div>
+        <label class="write-label">{{
+          "npcs.namePlaceholder" | translate
+        }}</label>
+        <div>
+          <mat-form-field class="name-write" appearance="outline">
+            <input
+              matInput
+              [(ngModel)]="currentNpc.name"
+              [placeholder]="'npcs.namePlaceholder' | translate"
+            />
+          </mat-form-field>
         </div>
       </div>
-    }
-  </div> -->
-
-  <!-- ss -->
-  <mat-accordion class="example-headers-align" multi>
-    <mat-expansion-panel [expanded]="true">
-      <mat-expansion-panel-header>
-        <mat-panel-title>
-          <div class="npc-list-title">Verbündete</div>
-        </mat-panel-title>
-      </mat-expansion-panel-header>
-      <divider [appearance]="'gold-2'" class="b-1"></divider>
-      <div class="npc-wrapper" (click)="addNpc('Ally')">
-        <div class="npc add-button">
-          <img src="assets/icons/UIIcons/add.svg" />
+      <div class="t-2">
+        <label class="write-label">{{ "npcs.short" | translate }}</label>
+        <div class="NgxEditor__Wrapper">
+          <ngx-editor-menu [editor]="shortEditor" [toolbar]="toolbar">
+          </ngx-editor-menu>
+          <ngx-editor
+            class="short-editor"
+            [editor]="shortEditor"
+            [placeholder]="'npcs.shortPlaceholder' | translate"
+            [(ngModel)]="currentNpc.shortDescription"
+          ></ngx-editor>
         </div>
       </div>
-      @for (npc of npcs.allies; let index = $index; track npc) {
-        <div class="npc-wrapper">
-          <div class="npc" (click)="selectNpc(index, 'allies')">
-            <div class="npc-title">{{ npc.name }}</div>
-          </div>
+      <div class="t-2">
+        <label class="write-label">{{ "npcs.long" | translate }}</label>
+        <div class="NgxEditor__Wrapper">
+          <ngx-editor-menu [editor]="longEditor" [toolbar]="toolbar">
+          </ngx-editor-menu>
+          <ngx-editor
+            class="long-editor"
+            [editor]="longEditor"
+            [placeholder]="'npcs.longPlaceholder' | translate"
+            [(ngModel)]="currentNpc.longDescription"
+          ></ngx-editor>
         </div>
-      }
-    </mat-expansion-panel>
-    <mat-expansion-panel [expanded]="true">
-      <mat-expansion-panel-header>
-        <mat-panel-title
-          ><div class="npc-list-title">Feinde</div></mat-panel-title
-        >
-      </mat-expansion-panel-header>
-      <!-- Enemies here -->
-    </mat-expansion-panel>
-
-    <mat-expansion-panel [expanded]="true">
-      <mat-expansion-panel-header>
-        <div class="npc-list-title">Andere</div>
-      </mat-expansion-panel-header>
-      <!-- Others here -->
-    </mat-expansion-panel>
-  </mat-accordion>
-</div>
-
-<!-- <div style="margin: 5rem 0 0 30rem">{{ currentNpc | json }}</div> -->
-
-<div class="read-container">
-  <div class="name">{{ currentNpc.name }}</div>
-  <divider [appearance]="'gold-2'" class="b-1"></divider>
-  <div class="short">{{ currentNpc.shortDescription }}</div>
-  <div class="long">{{ currentNpc.longDescription }}</div>
-</div>
+      </div>
+      <div class="button-row">
+        <ui-button width="w16" (click)="saveNpc()">{{
+          "npcs.save" | translate
+        }}</ui-button>
+        <ui-button width="w16" (click)="discardNpc()">{{
+          "npcs.discard" | translate
+        }}</ui-button>
+      </div>
+    </div>
+  } @else {
+    <div class="read-container">
+      <div class="title-row">
+        <div class="name-read">{{ currentNpc.name }}</div>
+        <icon-button
+          [icon]="'flip'"
+          (click)="showShortDescription = !showShortDescription"
+        ></icon-button>
+      </div>
+      <divider [appearance]="'gold-2'" class="b-1 t-1"></divider>
+      <div class="short-read">
+        <div
+          class="description"
+          [innerHTML]="
+            showShortDescription
+              ? currentNpc.shortDescription
+              : currentNpc.longDescription
+          "
+        ></div>
+      </div>
+    </div>
+  }
+} @else {
+  <div class="empty-container">
+    {{ "npcs.empty" | translate }}
+  </div>
+}

+ 144 - 32
src/app/journal/journal-npcs/journal-npcs.component.scss

@@ -8,11 +8,10 @@
   display: flex;
   flex-direction: column;
   align-items: center;
-  gap: 1rem;
   background-image: url("../../../assets/images/texture-10.jpg");
   border-right: var(--gold-3);
   box-shadow: var(--shadow);
-  padding-top: 3rem;
+  padding-top: 1.5rem;
 }
 
 .npc-list-title {
@@ -20,38 +19,62 @@
   font-weight: 500;
 }
 
-.npc-wrapper {
-  width: 16rem;
-  margin-bottom: 1rem;
+.npc {
+  width: 15rem;
+  padding: 1rem;
+  border: var(--gold-2);
+  border-radius: 10px;
+  box-shadow: var(--shadow);
+  cursor: pointer;
+  background-image: url("../../../assets/images/texture-0.jpg");
+  &:hover {
+    background-image: url("../../../assets/images/texture-5.jpg");
+  }
 
-  .npc {
+  .npc-title {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    font-weight: 500;
+  }
+
+  .unsaved {
+    margin-top: 0.375rem;
+    text-align: center;
+    color: rgb(52, 33, 33);
+    font-size: 0.75rem;
+    font-weight: 600;
+  }
+
+  &.active {
+    background-image: url("../../../assets/images/texture-30.jpg") !important;
     width: 14rem;
-    padding: 1rem;
-    border: var(--gold-2);
-    border-radius: 10px;
-    box-shadow: var(--shadow);
-    cursor: pointer;
-    background-image: url("../../../assets/images/texture-0.jpg");
-    &:hover {
-      background-image: url("../../../assets/images/texture-5.jpg");
-    }
+  }
+}
 
-    &.active {
+.npc-wrapper {
+  display: flex;
+  gap: 0.5rem;
+  align-items: center;
+
+  .control-button-wrapper {
+    display: none;
+    flex-direction: column;
+    gap: 0.5rem;
+  }
+
+  &.active:not(.edit-mode) {
+    .npc {
+      width: 11rem;
       background-image: url("../../../assets/images/texture-30.jpg") !important;
     }
-    .npc-title {
-      overflow: hidden;
-      white-space: nowrap;
-      text-overflow: ellipsis;
-      font-weight: 500;
+    .control-button-wrapper {
+      display: flex;
     }
-
-    .unsaved {
-      margin-top: 0.375rem;
-      text-align: center;
-      color: rgb(52, 33, 33);
-      font-size: 0.75rem;
-      font-weight: 600;
+  }
+  &.active {
+    .npc {
+      background-image: url("../../../assets/images/texture-30.jpg") !important;
     }
   }
   .add-button {
@@ -59,6 +82,10 @@
     display: flex;
     justify-content: center;
     align-items: center;
+
+    &.active {
+      background-image: url("../../../assets/images/texture-30.jpg") !important;
+    }
   }
 }
 
@@ -73,13 +100,68 @@
   border: var(--gold-3);
   background-image: url("/assets/images/texture-0.jpg");
   box-shadow: var(--shadow);
+
+  .title-row {
+    display: flex;
+    justify-content: space-between;
+
+    icon-button {
+      width: 2rem;
+      height: 2rem;
+    }
+  }
+
+  .name-read {
+    font-size: 1.5rem;
+    font-weight: 600;
+  }
+
+  .section-name {
+    font-size: 1.25rem;
+    font-weight: 500;
+  }
 }
 
-// MATERIAL
+// Write view
 
-// .mat-expansion-panel-header.mat-expanded {
-//   border-bottom: var(--gold-2);
-// }
+.write-container {
+  width: 800px;
+  height: calc(100vh - 2rem);
+  margin-top: 1.5rem;
+  margin-left: calc(50vw - 400px + 9rem);
+  padding: 1rem 2rem 2rem;
+  overflow: auto;
+
+  .name-write {
+    border: var(--gold-2);
+    border-radius: 6px;
+    box-shadow: var(--shadow);
+    ::ng-deep .mat-mdc-text-field-wrapper {
+      width: 26rem !important;
+      background-image: url("../../../assets/images/texture-0.jpg") !important;
+    }
+  }
+  .button-row {
+    display: flex;
+    justify-content: space-around;
+    margin-top: 2.5rem;
+  }
+}
+
+.write-label {
+  margin: 0 0 0.25rem 0.25rem;
+  font-weight: 600;
+}
+
+.empty-container {
+  width: 800px;
+  margin-top: 45vh;
+  margin-left: calc(50vw - 5rem);
+  font-weight: 600;
+  font-size: 1.5rem;
+}
+
+// MATERIAL
 
 .mat-expansion-panel {
   border: var(--gold-2);
@@ -92,3 +174,33 @@
 .mat-accordion {
   width: 16rem;
 }
+
+// Editor
+
+.NgxEditor__Wrapper {
+  border: var(--gold-3) !important;
+  border-radius: 6px;
+  box-shadow: var(--shadow);
+}
+
+::ng-deep .NgxEditor__MenuBar {
+  background-image: url("../../../assets/images/texture-10.jpg");
+}
+
+ngx-editor {
+  &.short-editor {
+    ::ng-deep .ProseMirror {
+      height: calc((100vh - 40rem) / 2);
+      background-image: url("../../../assets/images/texture-0.jpg");
+      border-radius: 0 0 4px 4px;
+    }
+  }
+
+  &.long-editor {
+    ::ng-deep .ProseMirror {
+      height: calc((100vh - 18rem) / 2);
+      background-image: url("../../../assets/images/texture-0.jpg");
+      border-radius: 0 0 4px 4px;
+    }
+  }
+}

+ 107 - 38
src/app/journal/journal-npcs/journal-npcs.component.ts

@@ -9,7 +9,8 @@ import { DataService } from 'src/services/data/data.service';
   styleUrl: './journal-npcs.component.scss',
 })
 export class JournalNpcsComponent {
-  editor: Editor = new Editor();
+  shortEditor: Editor = new Editor();
+  longEditor: Editor = new Editor();
   toolbar: any = [
     // default value
     ['bold', 'italic'],
@@ -17,44 +18,24 @@ export class JournalNpcsComponent {
     [{ heading: ['h3', 'h4', 'h5', 'h6'] }],
   ];
 
-  public isAlliesCollapsed = false;
-  public isEnemiesCollapsed = false;
-  public isOthersCollapsed = false;
-
-  private newNpcType: string = '';
+  public showShortDescription: boolean = false;
 
   /** Used to show the interactale form or the display version of an entry. */
   public isInEditMode = false;
 
   /** The index of the currently active entry */
   public currentIndex: number = 0;
-
   private backupIndex: number = -1;
+  public currentType: string = 'companions';
+
   /** Indicates, if the currentEntry is a newly generated one that is still not saved. */
   public isNewNpc = false;
+  public npcsIsEmpty = true;
 
   /**The array of JournalEntries, synched to the   */
   public npcs: Npcs = {
-    allies: [
-      {
-        name: 'Guardfield',
-        shortDescription: 'Ein ganz besonderer Guard',
-        longDescription:
-          'Man kann es gar nicht in Worte fassen, wie siehr wir alle diese Wache bewundern!',
-      },
-      {
-        name: 'Bob',
-        shortDescription: 'Ein ganz besonderer Guard',
-        longDescription:
-          'Man kann es gar nicht in Worte fassen, wie siehr wir alle diese Wache bewundern!',
-      },
-      {
-        name: 'Hans',
-        shortDescription: 'Ein ganz besonderer Guard',
-        longDescription:
-          'Man kann es gar nicht in Worte fassen, wie siehr wir alle diese Wache bewundern!',
-      },
-    ],
+    companions: [],
+    allies: [],
     enemies: [],
     others: [],
   };
@@ -67,16 +48,17 @@ export class JournalNpcsComponent {
   };
 
   private dataService: DataService = inject(DataService);
-  onInit(): void {
-    // this.npcs = this.dataService.npcsData;
-    // console.log('JournalNpcsComponent: ', this.npcs);
+
+  ngOnInit(): void {
+    this.npcs = this.dataService.npcs;
+    this.searchForNextNpc();
   }
 
   // FUNCTIONS
 
   public selectNpc(index: number, type: string): void {
     this.currentIndex = index;
-    this.newNpcType = type;
+    this.currentType = type;
     this.currentNpc = this.getNpc();
     this.isNewNpc = false;
     this.isInEditMode = false;
@@ -88,22 +70,109 @@ export class JournalNpcsComponent {
       shortDescription: '',
       longDescription: '',
     };
+    this.currentType = type;
     this.isInEditMode = true;
     this.isNewNpc = true;
     this.backupIndex = this.currentIndex;
-    this.newNpcType = type;
+    this.currentIndex = -1;
+    this.npcsIsEmpty = false;
+  }
+
+  /**
+   * Switches to the edit mode for the current entry.
+   */
+  public editNpc(): void {
+    this.isInEditMode = true;
+  }
+
+  public saveNpc(): void {
+    if (this.isNewNpc) {
+      // Prepend the new Npc
+      this.getList().unshift(this.currentNpc);
+      this.isNewNpc = false;
+      this.currentIndex = 0;
+    }
+    this.isInEditMode = false;
+    this.getList()[this.currentIndex] = this.currentNpc;
+    this.uploadNpcs();
+  }
+
+  public discardNpc(): void {
+    if (this.isNewNpc) {
+      this.currentIndex = this.backupIndex;
+      this.isNewNpc = false;
+    }
+    if (this.getList().length > 0) {
+      // Reset the currentEntry to the last saved version
+      this.currentNpc = this.getNpc();
+    } else {
+      this.searchForNextNpc();
+    }
+    this.isInEditMode = false;
   }
 
-  private getNpc(): Npc {
-    switch (this.newNpcType) {
+  /**
+   * Delets an npc in the current list at the current index and uploads the result to the server.
+   */
+  public deleteNpc(): void {
+    this.getList().splice(this.currentIndex, 1);
+    if (this.getList().length === 0) {
+      this.searchForNextNpc();
+    } else {
+      this.currentIndex = Math.max(this.currentIndex - 1, 0);
+      this.currentNpc = this.getNpc();
+    }
+    this.uploadNpcs();
+  }
+
+  // Utility functions
+
+  private getList(): Npc[] {
+    switch (this.currentType) {
+      case 'companions':
+        return this.npcs.companions;
       case 'allies':
-        return this.npcs.allies[this.currentIndex];
+        return this.npcs.allies;
       case 'enemies':
-        return this.npcs.enemies[this.currentIndex];
+        return this.npcs.enemies;
       case 'others':
-        return this.npcs.others[this.currentIndex];
+        return this.npcs.others;
       default:
-        return this.npcs.allies[this.currentIndex];
+        return this.npcs.allies;
     }
   }
+
+  public getNpc(): Npc {
+    switch (this.currentType) {
+      case 'companions':
+        return JSON.parse(
+          JSON.stringify(this.npcs.companions[this.currentIndex]),
+        );
+      case 'allies':
+        return JSON.parse(JSON.stringify(this.npcs.allies[this.currentIndex]));
+      case 'enemies':
+        return JSON.parse(JSON.stringify(this.npcs.enemies[this.currentIndex]));
+      case 'others':
+        return JSON.parse(JSON.stringify(this.npcs.others[this.currentIndex]));
+      default:
+        return JSON.parse(JSON.stringify(this.npcs.allies[this.currentIndex]));
+    }
+  }
+
+  private searchForNextNpc(): void {
+    let notFound = true;
+    ['companions', 'allies', 'enemies', 'others'].forEach((type) => {
+      if (this.npcs[type].length > 0 && notFound) {
+        this.currentType = type;
+        this.currentIndex = 0;
+        this.currentNpc = this.getNpc();
+        notFound = false;
+      }
+    });
+    this.npcsIsEmpty = notFound;
+  }
+
+  private uploadNpcs(): void {
+    this.dataService.npcs = this.npcs;
+  }
 }

+ 5 - 4
src/app/journal/journal.module.ts

@@ -19,11 +19,10 @@ import { MatTabsModule } from '@angular/material/tabs';
 import { MatExpansionModule } from '@angular/material/expansion';
 import { MatRadioModule } from '@angular/material/radio';
 import { MatDividerModule } from '@angular/material/divider';
-import {
-  MatCalendarCellClassFunction,
-  MatDatepickerModule,
-} from '@angular/material/datepicker';
+import { MatDatepickerModule } from '@angular/material/datepicker';
 import { MatNativeDateModule } from '@angular/material/core';
+import { MatButtonToggleModule } from '@angular/material/button-toggle';
+import { MatIconModule } from '@angular/material/icon';
 
 import { JournalRoutingModule } from './journal-routing.module';
 import { JournalHomeComponent } from './journal-home/journal-home.component';
@@ -204,6 +203,8 @@ import { DividerComponent } from '../shared-components/divider/divider.component
     MatDatepickerModule,
     MatNativeDateModule,
     NgbCollapseModule,
+    MatButtonToggleModule,
+    MatIconModule,
   ],
 })
 export class JournalModule {}

+ 23 - 3
src/assets/i18n/de.json

@@ -643,7 +643,7 @@
     "notes": "Notizen",
     "spellbook": "Zauberbuch",
     "quests": "Aufträge",
-    "npcs": "NPCs",
+    "npcs": "Personen",
     "places": "Orte",
     "maps": "Karten",
     "rules": "Regeln",
@@ -822,9 +822,29 @@
   },
   "notes": {
     "title": "Titel",
-    "placeholder": "Hier die Notizen einfügen"
+    "placeholder": "Hier die Notizen einfügen",
+    "save": "Speichern",
+    "discard": "Verwerfen",
+    "sessions": "Sessions",
+    "noName": "Noch kein Name",
+    "unsaved": "Nicht gespeichert",
+    "empty": "Noch kein Eintrag vorhanden"
+  },
+  "npcs": {
+    "characters": "Personen",
+    "companions": "Begleiter",
+    "enemies": "Feinde",
+    "allies": "Verbündete",
+    "others": "Andere",
+    "shortPlaceholder": "Kurze Beschreibung",
+    "longPlaceholder": "Ausführliche Beschreibung",
+    "namePlaceholder": "Name",
+    "short": "Kurzbeschreibung",
+    "long": "Ausführliche Beschreibung",
+    "empty": "Noch keine Personen hinzugefügt",
+    "save": "Speichern",
+    "discard": "Verwerfen"
   },
-
   "creator": {
     "new": "Neuen Charakter erstellen",
     "name": "Name",

+ 23 - 2
src/assets/i18n/en.json

@@ -638,7 +638,7 @@
     "notes": "Notes",
     "spellbook": "Spellbook",
     "quests": "Quests",
-    "npcs": "NPCs",
+    "npcs": "Persons",
     "places": "Places",
     "maps": "Maps",
     "rules": "Rules",
@@ -817,7 +817,28 @@
   },
   "notes": {
     "title": "Title",
-    "placeholder": "Add notes here"
+    "placeholder": "Add notes here",
+    "save": "Save",
+    "discard": "Discard",
+    "sessions": "Sessions",
+    "noName": "No Name yet",
+    "unsaved": "Not saved",
+    "empty": "No entries added yet"
+  },
+  "npcs": {
+    "characters": "Persons",
+    "companions": "Companions",
+    "enemies": "Enemies",
+    "allies": "Allies",
+    "others": "Others",
+    "shortPlaceholder": "Short description",
+    "longPlaceholder": "Detailed description",
+    "namePlaceholder": "Name",
+    "short": "Short Description",
+    "long": "Detailed Description",
+    "empty": "No persons added yet",
+    "save": "Save",
+    "discard": "Discard"
   },
   "creator": {
     "new": "Create New Character",

+ 1 - 1
src/assets/icons/UIIcons/edit.svg

@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-200h57l391-391-57-57-391 391v57Zm-80 80v-170l528-527q12-11 26.5-17t30.5-6q16 0 31 6t26 18l55 56q12 11 17.5 26t5.5 30q0 16-5.5 30.5T817-647L290-120H120Zm640-584-56-56 56 56Zm-141 85-28-29 57 57-29-28Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 -960 960 960" ><path d="M200-200h57l391-391-57-57-391 391v57Zm-80 80v-170l528-527q12-11 26.5-17t30.5-6q16 0 31 6t26 18l55 56q12 11 17.5 26t5.5 30q0 16-5.5 30.5T817-647L290-120H120Zm640-584-56-56 56 56Zm-141 85-28-29 57 57-29-28Z"/></svg>

+ 11 - 0
src/assets/icons/UIIcons/flip-2.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+
<g data-name="Layer 2">
+
<g data-name="flip-in">
+
<rect width="24" height="24" transform="rotate(-90 12 12)" opacity="0"/>
+
<path d="M5 6.09v12l-1.29-1.3a1 1 0 0 0-1.42 1.42l3 3a1 1 0 0 0 1.42 0l3-3a1 1 0 0 0 0-1.42 1 1 0 0 0-1.42 0L7 18.09v-12A1.56 1.56 0 0 1 8.53 4.5H11a1 1 0 0 0 0-2H8.53A3.56 3.56 0 0 0 5 6.09z"/>
+
<path d="M14.29 5.79a1 1 0 0 0 1.42 1.42L17 5.91v12a1.56 1.56 0 0 1-1.53 1.59H13a1 1 0 0 0 0 2h2.47A3.56 3.56 0 0 0 19 17.91v-12l1.29 1.3a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42l-3-3a1 1 0 0 0-1.42 0z"/>
+
</g>
+
</g>
+
</svg>

+ 1 - 0
src/assets/icons/UIIcons/flip.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24"   viewBox="0 0 1024 1024"><path d="M213.333333 259.84v512l-55.04-55.466667a42.666667 42.666667 0 0 0-60.586666 60.586667l128 128a42.666667 42.666667 0 0 0 60.586666 0l128-128a42.666667 42.666667 0 0 0 0-60.586667 42.666667 42.666667 0 0 0-60.586666 0L298.666667 771.84v-512A66.56 66.56 0 0 1 363.946667 192H469.333333a42.666667 42.666667 0 0 0 0-85.333333H363.946667A151.893333 151.893333 0 0 0 213.333333 259.84zM609.706667 247.04a42.666667 42.666667 0 0 0 60.586666 60.586667L725.333333 252.16v512a66.56 66.56 0 0 1-65.28 67.84H554.666667a42.666667 42.666667 0 0 0 0 85.333333h105.386666A151.893333 151.893333 0 0 0 810.666667 764.16v-512l55.04 55.466667a42.666667 42.666667 0 0 0 60.586666 0 42.666667 42.666667 0 0 0 0-60.586667l-128-128a42.666667 42.666667 0 0 0-60.586666 0z"  /></svg>

+ 2 - 0
src/interfaces/interfaces.ts

@@ -177,6 +177,8 @@ export interface JournalEntry {
 }
 
 export interface Npcs {
+  [key: string]: Npc[];
+  companions: Npc[];
   allies: Npc[];
   enemies: Npc[];
   others: Npc[];

+ 34 - 1
src/services/data/data.service.ts

@@ -11,6 +11,7 @@ import {
   Skill,
   Attribute,
   JournalEntry,
+  Npcs,
 } from 'src/interfaces/interfaces';
 import { SpellsService } from '../spells/spells.service';
 
@@ -149,12 +150,28 @@ export class DataService {
 
     // Notes
 
+    // Migration
     if (!Array.isArray(notesData.data)) {
       this.notesData = [];
       console.log('migrated notes data to array');
     } else {
       this.notesData = notesData.data;
     }
+
+    // NPCs
+
+    // Migration
+
+    if (npcsData.hasOwnProperty('companions')) {
+      this.npcs = npcsData;
+    } else {
+      this.npcs = {
+        companions: [],
+        allies: [],
+        enemies: [],
+        others: [],
+      };
+    }
   }
 
   // #endregion
@@ -1083,7 +1100,7 @@ export class DataService {
 
   // #endregion
 
-  // #region Notes
+  // #region Notes and Entries
 
   private _notesData: JournalEntry[] = [];
 
@@ -1096,6 +1113,22 @@ export class DataService {
     this.setData('notes', { data: newValue });
   }
 
+  private _npcsData: Npcs = {
+    companions: [],
+    allies: [],
+    enemies: [],
+    others: [],
+  };
+
+  public get npcs(): Npcs {
+    return this._npcsData;
+  }
+
+  public set npcs(newValue: Npcs) {
+    this._npcsData = newValue;
+    this.setData('npcs', newValue);
+  }
+
   // #endregion
 
   // #region database calls