Bläddra i källkod

Merge branch 'feature/spellcards' into develop

Warafear 1 år sedan
förälder
incheckning
860866748c
98 ändrade filer med 4947 tillägg och 2271 borttagningar
  1. 1 1
      .gitignore
  2. 974 0
      .nx/cache/d/daemon.log
  3. 1 1
      .nx/cache/d/server-process.json
  4. 2 0
      angular.json
  5. 822 15
      package-lock.json
  6. 2 2
      package.json
  7. 4 3
      src/app/app.module.ts
  8. 2 2
      src/app/character/character-creator/character-creator.component.html
  9. 256 63
      src/app/character/character-creator/character-creator.component.ts
  10. 2 1
      src/app/character/character-picker/character-picker.component.ts
  11. 7 1
      src/app/character/character.module.ts
  12. 3 1
      src/app/journal/journal-home/journal-home.component.ts
  13. 0 1
      src/app/journal/journal-home/modal/modal.component.ts
  14. 3 3
      src/app/journal/journal-home/navigation-panel/navigation-panel.component.html
  15. 12 8
      src/app/journal/journal-inventory/journal-inventory.component.ts
  16. 0 5
      src/app/journal/journal-inventory/simple-item-modal/simple-item-modal.component.ts
  17. 4 0
      src/app/journal/journal-routing.module.ts
  18. 38 0
      src/app/journal/journal-spellcards/add-card/add-card.component.html
  19. 51 0
      src/app/journal/journal-spellcards/add-card/add-card.component.scss
  20. 23 0
      src/app/journal/journal-spellcards/add-card/add-card.component.spec.ts
  21. 76 0
      src/app/journal/journal-spellcards/add-card/add-card.component.ts
  22. 29 230
      src/app/journal/journal-spellcards/journal-spellcards.component.html
  23. 101 43
      src/app/journal/journal-spellcards/journal-spellcards.component.scss
  24. 263 45
      src/app/journal/journal-spellcards/journal-spellcards.component.ts
  25. 5 0
      src/app/journal/journal-spellcards/spellcard/spellcard.component.html
  26. 21 0
      src/app/journal/journal-spellcards/spellcard/spellcard.component.scss
  27. 23 0
      src/app/journal/journal-spellcards/spellcard/spellcard.component.spec.ts
  28. 15 0
      src/app/journal/journal-spellcards/spellcard/spellcard.component.ts
  29. 4 4
      src/app/journal/journal-stats/ability-panel/ability-panel.component.ts
  30. 73 71
      src/app/journal/journal-stats/ability-panel/ability-table/ability-modal/ability-modal.component.html
  31. 16 2
      src/app/journal/journal-stats/ability-panel/ability-table/ability-modal/ability-modal.component.scss
  32. 46 68
      src/app/journal/journal-stats/ability-panel/ability-table/ability-modal/ability-modal.component.ts
  33. 21 13
      src/app/journal/journal-stats/ability-panel/ability-table/ability-table.component.html
  34. 7 14
      src/app/journal/journal-stats/ability-panel/ability-table/ability-table.component.scss
  35. 46 38
      src/app/journal/journal-stats/ability-panel/ability-table/ability-table.component.ts
  36. 0 10
      src/app/journal/journal-stats/ability-panel/proficiencies-table/proficiencies-table.component.html
  37. 22 21
      src/app/journal/journal-stats/ability-panel/proficiencies-table/proficiencies-table.component.ts
  38. 20 7
      src/app/journal/journal-stats/ability-panel/proficiencies-table/tools-modal/tools-modal.component.html
  39. 14 5
      src/app/journal/journal-stats/ability-panel/proficiencies-table/tools-modal/tools-modal.component.scss
  40. 16 27
      src/app/journal/journal-stats/ability-panel/proficiencies-table/tools-modal/tools-modal.component.ts
  41. 63 50
      src/app/journal/journal-stats/ability-panel/spellslots/spellslots-modal/spellslots-modal.component.html
  42. 13 0
      src/app/journal/journal-stats/ability-panel/spellslots/spellslots-modal/spellslots-modal.component.scss
  43. 33 26
      src/app/journal/journal-stats/ability-panel/spellslots/spellslots-modal/spellslots-modal.component.ts
  44. 22 8
      src/app/journal/journal-stats/ability-panel/spellslots/spellslots.component.html
  45. 33 19
      src/app/journal/journal-stats/ability-panel/spellslots/spellslots.component.ts
  46. 54 51
      src/app/journal/journal-stats/ability-panel/trait-table/trait-modal/trait-modal.component.html
  47. 16 2
      src/app/journal/journal-stats/ability-panel/trait-table/trait-modal/trait-modal.component.scss
  48. 34 46
      src/app/journal/journal-stats/ability-panel/trait-table/trait-modal/trait-modal.component.ts
  49. 14 23
      src/app/journal/journal-stats/ability-panel/trait-table/trait-table.component.html
  50. 39 31
      src/app/journal/journal-stats/ability-panel/trait-table/trait-table.component.ts
  51. 1 1
      src/app/journal/journal-stats/attribute-skill-container/attribute-panel/attribute-field/attribute-field.component.html
  52. 14 6
      src/app/journal/journal-stats/attribute-skill-container/attribute-panel/attribute-field/attribute-field.component.scss
  53. 0 21
      src/app/journal/journal-stats/attribute-skill-container/attribute-panel/attribute-field/attribute-field.component.ts
  54. 4 2
      src/app/journal/journal-stats/attribute-skill-container/save-throw-panel/save-throw-field/save-throw-field.component.html
  55. 7 2
      src/app/journal/journal-stats/attribute-skill-container/save-throw-panel/save-throw-field/save-throw-field.component.ts
  56. 9 9
      src/app/journal/journal-stats/attribute-skill-container/skill-panel/skill-field/skill-field.component.ts
  57. 9 9
      src/app/journal/journal-stats/attribute-skill-container/skill-panel/skill-panel.component.ts
  58. 1 0
      src/app/journal/journal-stats/info-row/conditions/conditions.component.html
  59. 4 2
      src/app/journal/journal-stats/info-row/conditions/conditions.component.ts
  60. 2 5
      src/app/journal/journal-stats/info-row/proficiency/proficiency-field.component.ts
  61. 1 0
      src/app/journal/journal-stats/life-container/life/life-details/life-details.component.html
  62. 2 5
      src/app/journal/journal-stats/life-container/life/life-details/life-details.component.ts
  63. 6 5
      src/app/journal/journal-stats/life-container/life/life.component.ts
  64. 32 8
      src/app/journal/journal-stats/weapons-container/spell-table/spell-table.component.html
  65. 64 25
      src/app/journal/journal-stats/weapons-container/spell-table/spell-table.component.scss
  66. 78 9
      src/app/journal/journal-stats/weapons-container/spell-table/spell-table.component.ts
  67. 26 6
      src/app/journal/journal-stats/weapons-container/weapon-table/weapon-modal/weapon-modal.component.html
  68. 13 16
      src/app/journal/journal-stats/weapons-container/weapon-table/weapon-modal/weapon-modal.component.scss
  69. 4 9
      src/app/journal/journal-stats/weapons-container/weapon-table/weapon-modal/weapon-modal.component.ts
  70. 19 14
      src/app/journal/journal-stats/weapons-container/weapon-table/weapon-table.component.html
  71. 4 3
      src/app/journal/journal-stats/weapons-container/weapon-table/weapon-table.component.ts
  72. 3 22
      src/app/journal/journal-stats/weapons-container/weapons-container.component.html
  73. 63 90
      src/app/journal/journal-stats/weapons-container/weapons-container.component.scss
  74. 0 2
      src/app/journal/journal-stats/weapons-container/weapons-container.component.ts
  75. 16 2
      src/app/journal/journal.module.ts
  76. 1 2
      src/app/journal/spell-modal/spell-modal.component.html
  77. 2 3
      src/app/journal/spell-modal/spell-modal.component.ts
  78. 130 0
      src/app/shared-components/full-spellcard/full-spellcard.component.html
  79. 62 0
      src/app/shared-components/full-spellcard/full-spellcard.component.scss
  80. 23 0
      src/app/shared-components/full-spellcard/full-spellcard.component.spec.ts
  81. 28 0
      src/app/shared-components/full-spellcard/full-spellcard.component.ts
  82. 2 1
      src/app/shared-components/shared-components.module.ts
  83. 1 0
      src/assets/icons/UIIcons/hidden.svg
  84. 1 0
      src/assets/icons/UIIcons/visible.svg
  85. BIN
      src/assets/images/spells/add-spell.jpg
  86. BIN
      src/assets/images/spells/guidance.jpg
  87. 19 11
      src/index.html
  88. 1 1
      src/interfaces/ability.ts
  89. 12 0
      src/interfaces/spell.ts
  90. 2 2
      src/interfaces/weapon.ts
  91. 417 886
      src/services/data/data.service.ts
  92. 16 0
      src/services/dataResolve/data-resolver.service.spec.ts
  93. 14 0
      src/services/dataResolve/data-resolver.service.ts
  94. 0 21
      src/services/database/database.service.ts
  95. 0 1
      src/services/details/details.service.ts
  96. 4 4
      src/services/spells/spells.service.spec.ts
  97. 395 0
      src/services/spells/spells.service.ts
  98. 89 105
      src/styles.scss

+ 1 - 1
.gitignore

@@ -19,7 +19,7 @@ yarn-error.log
 *.launch
 .settings/
 *.sublime-workspace
-.nx
+*.nx
 
 # Visual Studio Code
 .vscode/*

+ 974 - 0
.nx/cache/d/daemon.log

@@ -460752,3 +460752,977 @@ To fix this, set a unique name for each project in a project.json inside the pro
 [NX Daemon Server] - 2023-12-07T21:05:44.028Z - [WATCHER]: .nx/cache/d/daemon.log was modified
 [NX Daemon Server] - 2023-12-07T21:05:44.028Z - [WATCHER]: Processing file changes in outputs
 [NX Daemon Server] - 2023-12-07T21:05:44.029Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-07T21:27:24.724Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-07T21:27:24.727Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-07T21:27:24.733Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-07T21:27:24.735Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-07T21:27:24.735Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-07T21:27:24.736Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-07T21:27:24.826Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-07T21:27:24.827Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-07T21:27:24.828Z - Time taken for 'hash changed files from watcher' 36.47639998793602ms
+[NX Daemon Server] - 2023-12-07T21:27:24.829Z - [WATCHER]: .nx/cache/d/daemon.log was modified
+[NX Daemon Server] - 2023-12-07T21:27:24.829Z - [WATCHER]: Processing file changes in outputs
+[NX Daemon Server] - 2023-12-07T21:27:24.830Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-08T06:27:07.735Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-08T06:27:07.741Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-08T06:27:07.745Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-08T06:27:07.748Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-08T06:27:07.749Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-08T06:27:07.752Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-08T06:27:07.908Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-08T06:27:07.908Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-08T06:27:07.911Z - Time taken for 'hash changed files from watcher' 23.64079999923706ms
+[NX Daemon Server] - 2023-12-08T06:27:07.912Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-09T08:43:05.653Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-09T08:43:05.660Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-09T08:43:05.663Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-09T08:43:05.667Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-09T08:43:05.668Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-09T08:43:05.673Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-09T08:43:06.707Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-09T08:43:06.707Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-09T08:43:06.708Z - Time taken for 'hash changed files from watcher' 37.45649999380112ms
+[NX Daemon Server] - 2023-12-09T08:43:06.709Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-09T08:55:35.385Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-09T08:55:35.388Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-09T08:55:35.399Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-09T08:55:35.401Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-09T08:55:35.401Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-09T08:55:35.403Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-09T08:55:36.116Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-09T08:55:36.116Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-09T08:55:36.118Z - Time taken for 'hash changed files from watcher' 26.426199972629547ms
+[NX Daemon Server] - 2023-12-09T08:55:36.118Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-10T12:34:56.069Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-10T12:34:56.072Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-10T12:34:56.084Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-10T12:34:56.086Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-10T12:34:56.086Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-10T12:34:56.088Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-10T12:34:57.149Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-10T12:34:57.149Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-10T12:34:57.151Z - Time taken for 'hash changed files from watcher' 58.71040000021458ms
+[NX Daemon Server] - 2023-12-10T12:34:57.151Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-11T16:47:14.117Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-11T16:47:14.120Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-11T16:47:14.130Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-11T16:47:14.132Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-11T16:47:14.132Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-11T16:47:14.133Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-11T16:47:14.958Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-11T16:47:14.958Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-11T16:47:14.960Z - Time taken for 'hash changed files from watcher' 17.89919999241829ms
+[NX Daemon Server] - 2023-12-11T16:47:14.961Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-12T06:20:23.344Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-12T06:20:23.349Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-12T06:20:23.351Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-12T06:20:23.352Z - Established a connection. Number of open connections: 2
+[NX Daemon Server] - 2023-12-12T06:20:23.354Z - Closed a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-12T06:20:23.355Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-12T06:20:24.174Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-12T06:20:24.175Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-12T06:20:24.177Z - Time taken for 'hash changed files from watcher' 23.921300023794174ms
+[NX Daemon Server] - 2023-12-12T06:20:24.177Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-12T17:07:34.216Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-12T17:07:34.220Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-12T17:07:34.235Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-12T17:07:34.238Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-12T17:07:34.238Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-12T17:07:34.239Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-12T17:07:35.368Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-12T17:07:35.368Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-12T17:07:35.369Z - Time taken for 'hash changed files from watcher' 0.8055999875068665ms
+[NX Daemon Server] - 2023-12-12T17:07:35.370Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-12T17:35:05.837Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-12T17:35:05.841Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-12T17:35:05.861Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-12T17:35:05.864Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-12T17:35:05.864Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-12T17:35:05.866Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-12T17:35:06.233Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-12T17:35:06.233Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-12T17:35:06.236Z - Time taken for 'hash changed files from watcher' 10.856200009584427ms
+[NX Daemon Server] - 2023-12-12T17:35:06.238Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-13T06:36:25.242Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-13T06:36:25.244Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-13T06:36:25.257Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-13T06:36:25.259Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-13T06:36:25.259Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-13T06:36:25.260Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-13T06:36:26.180Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-13T06:36:26.180Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-13T06:36:26.181Z - Time taken for 'hash changed files from watcher' 28.93369996547699ms
+[NX Daemon Server] - 2023-12-13T06:36:26.181Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-13T14:16:23.005Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-13T14:16:23.009Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-13T14:16:23.020Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-13T14:16:23.023Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-13T14:16:23.023Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-13T14:16:23.025Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-13T14:16:23.837Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-13T14:16:23.837Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-13T14:16:23.838Z - Time taken for 'hash changed files from watcher' 26.586399999912828ms
+[NX Daemon Server] - 2023-12-13T14:16:23.839Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-13T15:51:42.880Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-13T15:51:42.888Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-13T15:51:42.895Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-13T15:51:42.897Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-13T15:51:42.898Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-13T15:51:42.900Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-13T15:51:43.045Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-13T15:51:43.046Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-13T15:51:43.048Z - Time taken for 'hash changed files from watcher' 33.83769999910146ms
+[NX Daemon Server] - 2023-12-13T15:51:43.048Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-14T07:47:18.276Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-14T07:47:18.299Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-14T07:47:18.302Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-14T07:47:18.307Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-14T07:47:18.313Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-14T07:47:18.321Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-14T07:47:18.618Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-14T07:47:18.618Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-14T07:47:18.624Z - Time taken for 'hash changed files from watcher' 9.143399998545647ms
+[NX Daemon Server] - 2023-12-14T07:47:18.625Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-14T07:55:31.264Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-14T07:55:31.268Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-14T07:55:31.270Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-14T07:55:31.272Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-14T07:55:31.272Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-14T07:55:31.274Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-14T07:55:32.278Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-14T07:55:32.278Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-14T07:55:32.279Z - Time taken for 'hash changed files from watcher' 11.233600000006845ms
+[NX Daemon Server] - 2023-12-14T07:55:32.280Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-14T14:36:09.300Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-14T14:36:09.311Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-14T14:36:09.312Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-14T14:36:09.313Z - Established a connection. Number of open connections: 2
+[NX Daemon Server] - 2023-12-14T14:36:09.315Z - Closed a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-14T14:36:09.316Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-14T14:36:10.100Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-14T14:36:10.100Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-14T14:36:10.101Z - Time taken for 'hash changed files from watcher' 26.30920000001788ms
+[NX Daemon Server] - 2023-12-14T14:36:10.102Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-14T17:21:04.391Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-14T17:21:04.397Z - [WATCHER]: Subscribed to changes within: C:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-14T17:21:04.402Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-14T17:21:04.404Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-14T17:21:04.404Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-14T17:21:04.406Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-14T17:21:04.495Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-14T17:21:04.495Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (C:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (C:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (C:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-14T17:21:04.498Z - Time taken for 'hash changed files from watcher' 27.734499998390675ms
+[NX Daemon Server] - 2023-12-14T17:21:04.500Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-14T19:50:24.002Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-14T19:50:24.005Z - [WATCHER]: Subscribed to changes within: C:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-14T19:50:24.012Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-14T19:50:24.015Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-14T19:50:24.015Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-14T19:50:24.018Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-14T19:50:24.113Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-14T19:50:24.113Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (C:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (C:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (C:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-14T19:50:24.115Z - Time taken for 'hash changed files from watcher' 40.100500002503395ms
+[NX Daemon Server] - 2023-12-14T19:50:24.116Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-14T20:19:22.262Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-14T20:19:22.264Z - [WATCHER]: Subscribed to changes within: C:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-14T20:19:22.274Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-14T20:19:22.276Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-14T20:19:22.277Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-14T20:19:22.278Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-14T20:19:22.351Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-14T20:19:22.351Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (C:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (C:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (C:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-14T20:19:22.352Z - Time taken for 'hash changed files from watcher' 26.725000001490116ms
+[NX Daemon Server] - 2023-12-14T20:19:22.354Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-15T07:29:32.292Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-15T07:29:32.295Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-15T07:29:32.296Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-15T07:29:32.297Z - Established a connection. Number of open connections: 2
+[NX Daemon Server] - 2023-12-15T07:29:32.299Z - Closed a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-15T07:29:32.301Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-15T07:29:32.898Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-15T07:29:32.898Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-15T07:29:32.899Z - Time taken for 'hash changed files from watcher' 26.047799989581108ms
+[NX Daemon Server] - 2023-12-15T07:29:32.900Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-15T12:12:13.256Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-15T12:12:13.258Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-15T12:12:13.264Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-15T12:12:13.266Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-15T12:12:13.266Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-15T12:12:13.268Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-15T12:12:13.763Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-15T12:12:13.763Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-15T12:12:13.764Z - Time taken for 'hash changed files from watcher' 28.11429999768734ms
+[NX Daemon Server] - 2023-12-15T12:12:13.765Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-15T14:01:44.350Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-15T14:01:44.354Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-15T14:01:44.370Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-15T14:01:44.372Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-15T14:01:44.372Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-15T14:01:44.374Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-15T14:01:45.124Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-15T14:01:45.124Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-15T14:01:45.125Z - Time taken for 'hash changed files from watcher' 0.8941999971866608ms
+[NX Daemon Server] - 2023-12-15T14:01:45.126Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-15T16:02:38.684Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-15T16:02:38.688Z - [WATCHER]: Subscribed to changes within: C:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-15T16:02:38.689Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-15T16:02:38.689Z - Established a connection. Number of open connections: 2
+[NX Daemon Server] - 2023-12-15T16:02:38.691Z - Closed a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-15T16:02:38.694Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-15T16:02:38.790Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-15T16:02:38.791Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (C:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (C:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (C:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (C:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-15T16:02:38.795Z - Time taken for 'hash changed files from watcher' 38.62120001018047ms
+[NX Daemon Server] - 2023-12-15T16:02:38.796Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-16T09:44:37.308Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-16T09:44:37.315Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-16T09:44:37.316Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-16T09:44:37.318Z - Established a connection. Number of open connections: 2
+[NX Daemon Server] - 2023-12-16T09:44:37.319Z - Closed a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-16T09:44:37.322Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-16T09:44:37.451Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-16T09:44:37.451Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-16T09:44:37.453Z - Time taken for 'hash changed files from watcher' 29.795899987220764ms
+[NX Daemon Server] - 2023-12-16T09:44:37.454Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-16T14:14:40.891Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-16T14:14:40.899Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-16T14:14:40.901Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-16T14:14:40.903Z - Established a connection. Number of open connections: 2
+[NX Daemon Server] - 2023-12-16T14:14:40.905Z - Closed a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-16T14:14:40.908Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-16T14:14:41.915Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-16T14:14:41.915Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-16T14:14:41.917Z - Time taken for 'hash changed files from watcher' 16.56920000910759ms
+[NX Daemon Server] - 2023-12-16T14:14:41.918Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-17T10:34:21.412Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-17T10:34:21.419Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-17T10:34:21.423Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-17T10:34:21.426Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-17T10:34:21.426Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-17T10:34:21.428Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-17T10:34:22.017Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-17T10:34:22.017Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-17T10:34:22.019Z - Time taken for 'hash changed files from watcher' 32.120700001716614ms
+[NX Daemon Server] - 2023-12-17T10:34:22.019Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-17T15:18:22.667Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-17T15:18:22.671Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-17T15:18:22.675Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-17T15:18:22.677Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-17T15:18:22.678Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-17T15:18:22.679Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-17T15:18:22.788Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-17T15:18:22.788Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-17T15:18:22.790Z - Time taken for 'hash changed files from watcher' 27.046000003814697ms
+[NX Daemon Server] - 2023-12-17T15:18:22.790Z - Done responding to the client null
+[NX Daemon Server] - 2023-12-17T17:53:05.875Z - Started listening on: \\.\pipe\nx\C:\Users\chris\AppData\Local\Temp\83d14e7134fc08a15480\d.sock
+[NX Daemon Server] - 2023-12-17T17:53:05.882Z - [WATCHER]: Subscribed to changes within: c:\Softwareprojekte\DnD (native)
+[NX Daemon Server] - 2023-12-17T17:53:05.884Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-17T17:53:05.886Z - Closed a connection. Number of open connections: 0
+[NX Daemon Server] - 2023-12-17T17:53:05.886Z - Established a connection. Number of open connections: 1
+[NX Daemon Server] - 2023-12-17T17:53:05.889Z - [REQUEST]: Client Request for Project Graph Received
+[NX Daemon Server] - 2023-12-17T17:53:05.994Z - Error detected when recomputing project file map: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+[NX Daemon Server] - 2023-12-17T17:53:05.994Z - [REQUEST]: Responding to the client with an error. Error when preparing serialized project graph. The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+Error: The following projects are defined in multiple locations:
+- DnDTools: 
+  - 
+  - .
+
+To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name.
+    at readProjectConfigurationsFromRootMap (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:97:15)
+    at buildProjectsConfigurationsFromProjectPathsAndPlugins (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\project-configuration-utils.js:70:19)
+    at createProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:131:129)
+    at WorkspaceContext.<anonymous> (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:82:39)
+    at getProjectConfigurationsFromContext (c:\Softwareprojekte\DnD\node_modules\nx\src\utils\workspace-context.js:26:29)
+    at _retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:81:72)
+    at retrieveProjectConfigurations (c:\Softwareprojekte\DnD\node_modules\nx\src\project-graph\utils\retrieve-workspace-files.js:58:12)
+    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
+    at async processCollectedUpdatedAndDeletedFiles (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:102:34)
+    at async processFilesAndCreateAndSerializeProjectGraph (c:\Softwareprojekte\DnD\node_modules\nx\src\daemon\server\project-graph-incremental-recomputation.js:138:17)
+[NX Daemon Server] - 2023-12-17T17:53:05.996Z - Time taken for 'hash changed files from watcher' 25.149699985980988ms
+[NX Daemon Server] - 2023-12-17T17:53:05.996Z - Done responding to the client null

+ 1 - 1
.nx/cache/d/server-process.json

@@ -1 +1 @@
-{"processId":26784}
+{"processId":16860}

+ 2 - 0
angular.json

@@ -30,6 +30,7 @@
               "src/assets"
             ],
             "styles": [
+              "@angular/material/prebuilt-themes/deeppurple-amber.css",
               "src/styles.scss"
             ],
             "scripts": []
@@ -93,6 +94,7 @@
               "src/assets"
             ],
             "styles": [
+              "@angular/material/prebuilt-themes/deeppurple-amber.css",
               "src/styles.scss"
             ],
             "scripts": []

+ 822 - 15
package-lock.json

@@ -14,6 +14,7 @@
         "@angular/compiler": "^17.0.3",
         "@angular/core": "^17.0.3",
         "@angular/forms": "^17.0.3",
+        "@angular/material": "^17.0.0",
         "@angular/platform-browser": "^17.0.3",
         "@angular/platform-browser-dynamic": "^17.0.3",
         "@angular/router": "^17.0.3",
@@ -21,7 +22,6 @@
         "@popperjs/core": "^2.11.6",
         "bootstrap": "^5.2.3",
         "localbase": "^0.7.5",
-        "ngx-smart-modal": "^14.0.2",
         "rxjs": "~7.8.0",
         "tslib": "^2.3.0",
         "zone.js": "~0.14.2"
@@ -508,6 +508,70 @@
         "@angular/compiler-cli": "17.0.3"
       }
     },
+    "node_modules/@angular/material": {
+      "version": "17.0.0",
+      "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.0.0.tgz",
+      "integrity": "sha512-rd7H7NhkDPDiyLHADm2FHOJlmgaWV7ZYNYPe/4yTXlt++GTSLhKus+PTCZYVsKGlA3mxDhNnC1RY+fdjtx/G2A==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/auto-init": "15.0.0-canary.a246a4439.0",
+        "@material/banner": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/button": "15.0.0-canary.a246a4439.0",
+        "@material/card": "15.0.0-canary.a246a4439.0",
+        "@material/checkbox": "15.0.0-canary.a246a4439.0",
+        "@material/chips": "15.0.0-canary.a246a4439.0",
+        "@material/circular-progress": "15.0.0-canary.a246a4439.0",
+        "@material/data-table": "15.0.0-canary.a246a4439.0",
+        "@material/density": "15.0.0-canary.a246a4439.0",
+        "@material/dialog": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/drawer": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/fab": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/floating-label": "15.0.0-canary.a246a4439.0",
+        "@material/form-field": "15.0.0-canary.a246a4439.0",
+        "@material/icon-button": "15.0.0-canary.a246a4439.0",
+        "@material/image-list": "15.0.0-canary.a246a4439.0",
+        "@material/layout-grid": "15.0.0-canary.a246a4439.0",
+        "@material/line-ripple": "15.0.0-canary.a246a4439.0",
+        "@material/linear-progress": "15.0.0-canary.a246a4439.0",
+        "@material/list": "15.0.0-canary.a246a4439.0",
+        "@material/menu": "15.0.0-canary.a246a4439.0",
+        "@material/menu-surface": "15.0.0-canary.a246a4439.0",
+        "@material/notched-outline": "15.0.0-canary.a246a4439.0",
+        "@material/radio": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/segmented-button": "15.0.0-canary.a246a4439.0",
+        "@material/select": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/slider": "15.0.0-canary.a246a4439.0",
+        "@material/snackbar": "15.0.0-canary.a246a4439.0",
+        "@material/switch": "15.0.0-canary.a246a4439.0",
+        "@material/tab": "15.0.0-canary.a246a4439.0",
+        "@material/tab-bar": "15.0.0-canary.a246a4439.0",
+        "@material/tab-indicator": "15.0.0-canary.a246a4439.0",
+        "@material/tab-scroller": "15.0.0-canary.a246a4439.0",
+        "@material/textfield": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tooltip": "15.0.0-canary.a246a4439.0",
+        "@material/top-app-bar": "15.0.0-canary.a246a4439.0",
+        "@material/touch-target": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.3.0"
+      },
+      "peerDependencies": {
+        "@angular/animations": "^17.0.0 || ^18.0.0",
+        "@angular/cdk": "17.0.0",
+        "@angular/common": "^17.0.0 || ^18.0.0",
+        "@angular/core": "^17.0.0 || ^18.0.0",
+        "@angular/forms": "^17.0.0 || ^18.0.0",
+        "@angular/platform-browser": "^17.0.0 || ^18.0.0",
+        "rxjs": "^6.5.3 || ^7.4.0"
+      }
+    },
     "node_modules/@angular/platform-browser": {
       "version": "17.0.3",
       "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.0.3.tgz",
@@ -3764,6 +3828,758 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/@material/animation": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-0eV06UGYeuFwC/4t+yjg3LCRGRLq72ybBtJYzcBDpP4ASTjie0WmpAOFJYXRq2U5X/yxLviDMhpRemoSUjgZ0Q==",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/auto-init": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-0QfmjT5elQ10hCxToVgq/WaC3301tVH1sJaO3O2yocVzr7s6iWm8/zch16V5hcHzQHbtcT3Rf4y1ZzmdNys2Iw==",
+      "dependencies": {
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/banner": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-PBLgH7JEbEpTkLy33oyWXUhIFmSsdOrR6Gn6qIgQRo1qrnk5RSBGW2gEq4Z6793vjxM107gKudDb23E4Fcu4vg==",
+      "dependencies": {
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/button": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/base": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-/ob3v3IFU8q2gGdVNWw5kNPjW2mRTeBIz1YdhGWUmRxKn2Kl8bdLOvrAmZtQMmPn/4cGXvinxpec/zVBWQKDkA==",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/button": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-rGpVRde0Aqhv2t9QvT8Zl3HvG89BeUNPOpgfpaLBZ4SGGAO4rIrckl/eCENibKgmmdCKcYZlG9gc5abQVPfUvw==",
+      "dependencies": {
+        "@material/density": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/focus-ring": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "@material/touch-target": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/card": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-+rYUnBPgv5QVF6BeUs3toIRdSwFVohGmjk2ptTXMZkKxqAJt7Nr9Znbm3Ym2hD8GUHJeh3pyGFvEs6rG6JMYAw==",
+      "dependencies": {
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/checkbox": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-sQwHzm1TSxHUoPrqplWTk/BhyzdDhzcwlbucwJK9W0o9WXMDk+d9PvcCxpP/9sAnVqZk42BfE89Y0T1DHglZ9A==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/density": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/focus-ring": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/touch-target": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/chips": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-TiV9WJ5taEHPGWPhXbxJvUJhLzThg+VpK7aAlvL4RurtmJ7pURuEdRS4Z6o0OEqi3wKQ4z/+K44kZUn/+9HALg==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/checkbox": "15.0.0-canary.a246a4439.0",
+        "@material/density": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/focus-ring": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "@material/touch-target": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "safevalues": "^0.3.4",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/circular-progress": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-+QTfyExPWzgm2tqMInd32qQOftsC1b8MUhAhZSfuecYBfqAc7KZkQEKa2nm4y8EHKMFWe8/DcxLV6IxMBLgHwA==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/progress-indicator": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/data-table": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-89qVOjR7gqby6fsmh7tKj29SjQ2sGLXu2IzCeX3Vni4mz+xxo5dv11jxYNADvdgJDfhyDJFPh1FlqAH7O09nFA==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/checkbox": "15.0.0-canary.a246a4439.0",
+        "@material/density": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/icon-button": "15.0.0-canary.a246a4439.0",
+        "@material/linear-progress": "15.0.0-canary.a246a4439.0",
+        "@material/list": "15.0.0-canary.a246a4439.0",
+        "@material/menu": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/select": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "@material/touch-target": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/density": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-h8BJVCWkPR97WeWCN6/atVbSOP8J4+ZbbssidcwsnX7b3+3IaWdtBxGii25dsILX8pUVwwqxVis24y211b+8rg==",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/dialog": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-4lyxd+5ccOEMUGKzZcssaYyzkCsYTpYCSQSANR0toQPLv3voDwKMfA709uZI6+nL7Re6Xdf7jx8qe+QpTTjVcw==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/button": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/icon-button": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "@material/touch-target": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/dom": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-AftSOGQoQg/Ys2kOVjZzvqWmsnhg3Kam/2UC4Gj0DMMCu36J4MAoD+3PpnOd1aG3wiJKtUXR2vPIwE8I/PM9yg==",
+      "dependencies": {
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/drawer": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-/JUmbzRBaikdbZ250yA9ZTPqp2W5nGvvuHYoNVAAmtOmxuwGvvNNpWiVZy2lIYeYcf1hA7hJ5mEQxs0aSD7iWQ==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/list": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/elevation": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-lwPIOb8fHyOljIWYcVLPT73dPIEOKat/CXu6gqYIVMQgZQIksQNUA7z1O3l7apkRSuYUOYSXqrgU7AnWP4KcJg==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/fab": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-XUex3FNqxPD1i/4jITucB/RWTNkkdv52mbNmwrvbuThZlhuhyH9GzOQYTDop/b2783TPcv++xr8UUbuh8GWYzA==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/focus-ring": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "@material/touch-target": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/feature-targeting": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-/SU9X5y8CRp6RS9qnjnM/N5qfsJ8bYILpR841eZmN6DLqMupaM9Yy7Mx8+v/QvpBLLhk+jmu79nFzwkwW54d6Q==",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/floating-label": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-832qZ/qxKx0KUatoeVY3Q2NmboVgiWBG0/1VsbJyodHrgQWfnBOHgLE+M322o6uM3OhvO+kWm4iYbvwhmLZGsw==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/focus-ring": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-ar0BtACFS3K14k/enAg0ePeEA/f/RJY4Ji4L/00Dw/B3XVpNRbqLH49jkcbtcQjdTS0FEyk2sWSNMZl6wVi0/A==",
+      "dependencies": {
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0"
+      }
+    },
+    "node_modules/@material/form-field": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-Q/+ErgtAUFUPPUmWA1m5IP5voiN8XjPRwyoAlFxSTa/4t+EA5B18Z8Bsn9b6I0AC8RHke06H7UWrKz8XUDIFpw==",
+      "dependencies": {
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/icon-button": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-Igyo94rkIlqC91BR1Tv+WLTz1ZWcZZjl1xU7Vsx8mbWA1PnaRDUTNVV5LFi4e0ORp6GSblFTImpHngEy4agMEg==",
+      "dependencies": {
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/density": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/focus-ring": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/touch-target": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/image-list": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-Rcj3q7Tp7Nwbe5ht6ptTc3zqK8TSDJHaPDBf+kzi0kkh6MAB4qoHPgn+HnA+zIZ79CScU56bN7zjA6XYaZvsLw==",
+      "dependencies": {
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/layout-grid": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-bkfxZuVzgtjEJgR3n8pvDQbe88ffULDJ5d2DF34IR8SOiRmQcj7UzqAt95XwIUcWlfisLCoIryP4U8XSpFb1EQ==",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/line-ripple": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-20WmwRrejmtOdI37+959UqEVIjbMtAXlkDOkfCIA3OUhp+oZSjVkCqKxI16jxxVlnzJ353fy8xeSKzOHe4sExQ==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/linear-progress": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-IcCd4476pXHloTYadHDJ+2c2lntoVigeNnQEiD/ASQTKqKrJqkIdvvczFm9Ryu+V2+TKhp7vvQGFLUMaLPcmhw==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/progress-indicator": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/list": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-4H5dKIjCUGIPmKjfcegV0SBybD5NNdHp26OU6sovvWIvxSGQtDJr6z9I7i+0vF/HIS5ScbHD2+9/txtL80iqCA==",
+      "dependencies": {
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/density": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/menu": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-2HOHQAIdWQtXjSvEIrW3lnbcIwFf5XaQhFzCEZ04FcSGApc4iLwsmRFVW3PzWx+mVrUrEfO/K42DVULIX9J1Pg==",
+      "dependencies": {
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/list": "15.0.0-canary.a246a4439.0",
+        "@material/menu-surface": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/menu-surface": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-4h4wZ0Rs7qBg1Otldw8ljp+LCULNL42pqbqcTXhKAkJM7pHcSw4k7IfoThSRLU3+V8T3/+qiAXyeQix2OGHzwg==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/notched-outline": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-zmRZHJ+5cOWsBatRyK50wuht78olXySyKOJIIEmy8lxSMZefI1764u0mr8tS1KYF8vSAl5cUlwCC3/2Njz1FPg==",
+      "dependencies": {
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/floating-label": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/progress-indicator": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-92HM5niUnqG5Y3M/xkscBD+2lkaWPDcIRPo0RHPYcyldL+EhWRv/sdQpfdiXw/h3uvKSowKxBMCHm8krAyf+sQ==",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/radio": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-on8EVztWXc/ajcaowFZ31ClGADYxQrhj4ulMne0NxdHHWQ44ttf5aXOVqtv5mxeOzrRACOkQyTUXBG07yTWCEQ==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/density": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/focus-ring": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/touch-target": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/ripple": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-Vl615/PIBpBD+IOI9Xypz0SV3RsmYJYSNx890Rih7irhUOaPsOUBmTYOWF5AsGBynqLcXoTNVhK92drYLKtJwQ==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/rtl": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-pgJFw8ZRpWGpwv7ZuBTJ+WdNmFBKoLVoMbbxKQWTHXVwhAqn3aoIq95o62T5QeEG/+sguNShdquG45CpAMmSRw==",
+      "dependencies": {
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/segmented-button": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-oqGHs2C7C+yJW/xZf/wP8jBGLs6HcerhM3CsorLAEMH3MGuIlVC17WcisBewEWucsILYEWbySXy/7T4h6/psZA==",
+      "dependencies": {
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/touch-target": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/select": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-odoNLiVOgdwbEeePkjHtlr43pjskDwyO8hi4z3jcud1Rg1czk5zoJ2mUI0+olOJjBQ26PGocwrSLqf3qaThbIA==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/density": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/floating-label": "15.0.0-canary.a246a4439.0",
+        "@material/line-ripple": "15.0.0-canary.a246a4439.0",
+        "@material/list": "15.0.0-canary.a246a4439.0",
+        "@material/menu": "15.0.0-canary.a246a4439.0",
+        "@material/menu-surface": "15.0.0-canary.a246a4439.0",
+        "@material/notched-outline": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/shape": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-rcWPlCoHyP79ozeEKk73KWt9WTWdh6R68+n75l08TSTvnWZB5RRTmsI9BMkz55O9OJD/8H8ZsOxBe4x2QXUT7w==",
+      "dependencies": {
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/slider": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-is1BSBpxaXBBv+wSVpe9WGWmWl59yJEeDNubTES2UFD0er3BmA+PdKkL09vvytDnBcbKf77TbxaRiUSGVaKUQA==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/snackbar": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-2NAtC1qozR/uajszZnPy08Ej8HNnpgvCjNCBerDN4SLH2Q0/aWrVrUjqRCp2ayAvsX+szoroGbCboMhaWRzDuQ==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/button": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/icon-button": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/switch": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-o0wcbYgm2yRs4een5uxT4RJnJ003DxXe33rk8vTBG2o7cdiSR3X7GJQxeIK3D9wPgWCAwBLhNYSzXrlTL5pkMw==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/density": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/focus-ring": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "safevalues": "^0.3.4",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/tab": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-HGLK774uMeLnhbjDJBOjft7S6SurZnKb+6Und88OMDUVUEG6MkFBAKQQr09iBIeLE2sUAiGQhBVQtb7LJKwolQ==",
+      "dependencies": {
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/focus-ring": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/tab-indicator": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/tab-bar": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-dMQb1vXsBchQXcjbwgJZIGqTZHngm+3QGSOSb4LWjqHIgC5+w2RRrHsIAjNTyRhKssJ9nKKrbpM/Yz5vTPWH6w==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/density": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/tab": "15.0.0-canary.a246a4439.0",
+        "@material/tab-indicator": "15.0.0-canary.a246a4439.0",
+        "@material/tab-scroller": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/tab-indicator": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-gG2BgHT+ggKnUOaT8LjmH/+9nknRLh8v9qemrhUkDuCtZ8inlaC33OVbbxfrpQW3J+UzBh5YCUSC+2KrN39uUA==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/tab-scroller": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-6KvBpalc4SwLbHFm0rnuIE64VffUj7AKhnPc+mqM6VmxOvDzQ/ZSYga0rWlUfM4mCDFX3ZkSxim+iNzVF+Ejaw==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/tab": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/textfield": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-4BW5bUERPlIeiPnLSby21h1/xDmySuAG9Ucn1LM801a0+5mK3IwWb8031AP3filKZZqTx5JJvOJYZd6/OWBJVA==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/density": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/floating-label": "15.0.0-canary.a246a4439.0",
+        "@material/line-ripple": "15.0.0-canary.a246a4439.0",
+        "@material/notched-outline": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/theme": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-HWxC5Nhz8JZKTLTVmAsNxIGB3Kzr53+YFMg327S8/XuEDmI0RFHFvtwM9rADmyrHFBmUaVhV4iELyxFdi67c9w==",
+      "dependencies": {
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/tokens": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-+5iGfQ51YSb0Qau8uC6/jHXCSC3enKaQKDf/iPHfuXAe04UznW3tmm1/Ju227aZXNISTJcnQYa2rpm1M14MeUg==",
+      "dependencies": {
+        "@material/elevation": "15.0.0-canary.a246a4439.0"
+      }
+    },
+    "node_modules/@material/tooltip": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-Ja2Z4aZQkYWD6InXA+MG4M9zdKR6dYsXXlYzQppYpfcQzXylZqh5Y7WBLulG5fA2o83pHVwILfwFZM7j7ht08Q==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/button": "15.0.0-canary.a246a4439.0",
+        "@material/dom": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/tokens": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "safevalues": "^0.3.4",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/top-app-bar": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-twQchmCa1In/FFrALPYojgeM8vmV7KH96wRY9NmPSJ046ANgPCicLBgLuSzrLETCFqAwbztqzxSG4xMBL81rYg==",
+      "dependencies": {
+        "@material/animation": "15.0.0-canary.a246a4439.0",
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/elevation": "15.0.0-canary.a246a4439.0",
+        "@material/ripple": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/shape": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "@material/typography": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/touch-target": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-ubyD1TUjZnRPEdDnk6Lrcm2ZsjnU7CV5y7IX8pj9IPawiM6bx4FkjZBxUvclbv3WiTGk5UOnwPOySYAJYAMQ1w==",
+      "dependencies": {
+        "@material/base": "15.0.0-canary.a246a4439.0",
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/rtl": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/@material/typography": {
+      "version": "15.0.0-canary.a246a4439.0",
+      "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.a246a4439.0.tgz",
+      "integrity": "sha512-eXzBl9ROzWZ+41nan5pCrn1C/Zq3o/VsrLFaGv8fdRmhRR6/wHMeuvCCwGf5VtEmWdAE9FpJzRU/4ZPiJCJUyg==",
+      "dependencies": {
+        "@material/feature-targeting": "15.0.0-canary.a246a4439.0",
+        "@material/theme": "15.0.0-canary.a246a4439.0",
+        "tslib": "^2.1.0"
+      }
+    },
     "node_modules/@ng-bootstrap/ng-bootstrap": {
       "version": "16.0.0-rc.2",
       "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-16.0.0-rc.2.tgz",
@@ -16294,20 +17110,6 @@
       "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
       "dev": true
     },
-    "node_modules/ngx-smart-modal": {
-      "version": "14.0.2",
-      "resolved": "https://registry.npmjs.org/ngx-smart-modal/-/ngx-smart-modal-14.0.2.tgz",
-      "integrity": "sha512-qcvjtaNQ39KBTKOeRcyFWciOIOD1NtSLxuYtRE0lOrrcKPNfASEWiErLpvFQZf2Vh/yTJfVyZnzy/9WEYBgWuA==",
-      "dependencies": {
-        "tslib": "^2.3.0"
-      },
-      "peerDependencies": {
-        "@angular/common": ">=14.2.0",
-        "@angular/core": ">=14.2.0",
-        "@angular/platform-browser": ">=14.2.0",
-        "zone.js": ">=0.11.4"
-      }
-    },
     "node_modules/nice-napi": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
@@ -18918,6 +19720,11 @@
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
       "dev": true
     },
+    "node_modules/safevalues": {
+      "version": "0.3.4",
+      "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz",
+      "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw=="
+    },
     "node_modules/sass": {
       "version": "1.69.5",
       "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz",

+ 2 - 2
package.json

@@ -17,6 +17,7 @@
     "@angular/compiler": "^17.0.3",
     "@angular/core": "^17.0.3",
     "@angular/forms": "^17.0.3",
+    "@angular/material": "^17.0.0",
     "@angular/platform-browser": "^17.0.3",
     "@angular/platform-browser-dynamic": "^17.0.3",
     "@angular/router": "^17.0.3",
@@ -24,7 +25,6 @@
     "@popperjs/core": "^2.11.6",
     "bootstrap": "^5.2.3",
     "localbase": "^0.7.5",
-    "ngx-smart-modal": "^14.0.2",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",
     "zone.js": "~0.14.2"
@@ -49,4 +49,4 @@
     "nx": "17.0.2",
     "typescript": "~5.2.2"
   }
-}
+}

+ 4 - 3
src/app/app.module.ts

@@ -1,10 +1,10 @@
 import { NgModule } from '@angular/core';
 import { BrowserModule } from '@angular/platform-browser';
-// import { CdkTableModule } from '@angular/cdk/table';
 import { AppRoutingModule } from './app-routing.module';
 import { AppComponent } from './app.component';
 import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
-import { NgxSmartModalModule } from 'ngx-smart-modal';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { MatSlideToggleModule } from '@angular/material/slide-toggle';
 
 @NgModule({
   declarations: [AppComponent],
@@ -12,7 +12,8 @@ import { NgxSmartModalModule } from 'ngx-smart-modal';
     BrowserModule,
     AppRoutingModule,
     NgbModule,
-    NgxSmartModalModule.forRoot(),
+    BrowserAnimationsModule,
+    MatSlideToggleModule,
   ],
   providers: [],
   bootstrap: [AppComponent],

+ 2 - 2
src/app/character/character-creator/character-creator.component.html

@@ -1,7 +1,7 @@
 <div>
   <div>
-    <label>Character Name</label>
+    <label>Name</label>
     <input [(ngModel)]="characterName" />
   </div>
-  <button (click)="createCharacter()">Create Character</button>
+  <button (click)="createCharacter()">Neuen Charakter erstellen</button>
 </div>

+ 256 - 63
src/app/character/character-creator/character-creator.component.ts

@@ -8,24 +8,30 @@ import { Router } from '@angular/router';
   styleUrls: ['./character-creator.component.scss'],
 })
 export class CharacterCreatorComponent {
-  public constructor(public dataService: DataService, private Router: Router) {}
+  public constructor(
+    public dataAccessor: DataService,
+    private Router: Router
+  ) {}
 
   public characterName: string = '';
 
   public async createCharacter(): Promise<void> {
     // Creates a new entry in the character collection
-    this.dataService.addData('characters', { name: this.characterName });
-    // TODO: Animation, dass der Character erstellt wurde
-    await this.createNewCharacterInDatabase();
-    // Die Funktion muss ertstmal durchlaufen, bevor der Character ausgewählt werden kann
-    this.dataService.selectCharacter(this.characterName);
+    this.dataAccessor.addData('characters', { name: this.characterName });
+    // Creates a new collection with the character name
+    this.createNewCharacterInDatabase().then(() => {
+      // Die Funktion muss ertstmal durchlaufen, bevor der Character ausgewählt werden kann
+      sessionStorage.setItem('characterName', this.characterName);
+      this.dataAccessor.dataLoaded = false;
+      this.Router.navigate(['journal']);
+    });
   }
 
   public async createNewCharacterInDatabase(): Promise<void> {
     // TODO: Für alle Daten einen eigenen Key/Value Eintrag anlegen addData(collection: string, data: any, key?: string): void
     return Promise.all([
       // Character Data
-      this.dataService.addData(
+      this.dataAccessor.addData(
         this.characterName,
         {
           name: this.characterName,
@@ -41,64 +47,136 @@ export class CharacterCreatorComponent {
         },
         'characterData'
       ),
-
       // Character Attributes
-      this.dataService.addData(
+      this.dataAccessor.addData(
         this.characterName,
         {
-          strength: ['', false],
-          dexterity: ['', false],
-          constitution: ['', false],
-          intelligence: ['', false],
-          wisdom: ['', false],
-          charisma: ['', false],
+          strength: { name: 'strength', value: 10, proficiency: false },
+          dexterity: { name: 'dexterity', value: 10, proficiency: false },
+          constitution: { name: 'constitution', value: 10, proficiency: false },
+          intelligence: { name: 'intelligence', value: 10, proficiency: false },
+          wisdom: { name: 'wisdom', value: 10, proficiency: false },
+          charisma: { name: 'charisma', value: 10, proficiency: false },
         },
-        'attribute'
+        'attributes'
       ),
-
       // Character Skills
-      this.dataService.addData(
-        this.characterName,
-        {
-          acrobatics: ['', false],
-          animalHandling: ['', false],
-          arcana: ['', false],
-          athletics: ['', false],
-          deception: ['', false],
-          history: ['', false],
-          insight: ['', false],
-          intimidation: ['', false],
-          investigation: ['', false],
-          medicine: ['', false],
-          nature: ['', false],
-          perception: ['', false],
-          performance: ['', false],
-          persuasion: ['', false],
-          religion: ['', false],
-          sleightOfHand: ['', false],
-          stealth: ['', false],
-          survival: ['', false],
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          acrobatics: { name: 'acrobatics', proficiency: false },
+          animalHandling: { name: 'animalHandling', proficiency: false },
+          arcana: { name: 'arcana', proficiency: false },
+          athletics: { name: 'athletics', proficiency: false },
+          deception: { name: 'deception', proficiency: false },
+          history: { name: 'history', proficiency: false },
+          insight: { name: 'insight', proficiency: false },
+          intimidation: { name: 'intimidation', proficiency: false },
+          investigation: { name: 'investigation', proficiency: false },
+          medicine: { name: 'medicine', proficiency: false },
+          nature: { name: 'nature', proficiency: false },
+          perception: { name: 'perception', proficiency: false },
+          performance: { name: 'performance', proficiency: false },
+          persuasion: { name: 'persuasion', proficiency: false },
+          religion: { name: 'religion', proficiency: false },
+          sleightOfHand: { name: 'sleightOfHand', proficiency: false },
+          stealth: { name: 'stealth', proficiency: false },
+          survival: { name: 'survival', proficiency: false },
         },
         'skills'
       ),
       // Character Combat Stats
-      this.dataService.addData(
+      this.dataAccessor.addData(
         this.characterName,
         {
-          armorClass: '',
-          initiative: '',
-          speed: '',
-          hitPointMaximum: '',
-          currentHitPoints: '',
-          temporaryHitPoints: '',
-          hitDice: '',
-          deathSaveSuccesses: '',
-          deathSaveFailures: '',
+          armorClass: 10,
+          initiative: 0,
+          movement: 30,
+          deathSaves: [0, 0],
+          proficiencyBonus: 2,
+          conditions: [],
+          exhaustion: 0,
+          inspiration: false,
         },
         'combatStats'
       ),
+      //  Character Hit Points
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          hitPoints: {
+            maxHitPoints: 10,
+            currentHitPoints: 10,
+            temporaryHitPoints: 0,
+          },
+          hitDice: {
+            hitDiceNumber: 1,
+            hitDiceType: 10,
+          },
+        },
+        'hitPoints'
+      ),
+
+      // Character Abilities
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          data: [],
+        },
+        'abilities'
+      ),
+
+      // Character Traits
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          data: [],
+        },
+        'traits'
+      ),
+
+      // Character Proficiencies
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          armor: {
+            light: false,
+            medium: false,
+            heavy: false,
+          },
+          weapons: {
+            simple: false,
+            martial: false,
+            other: [],
+          },
+          tools: [],
+          languages: ['Gemeinsprache'],
+        },
+        'proficiencies'
+      ),
+
+      // Character Spellslots
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          spellslots: [],
+          showSpellslots: false,
+        },
+        'spellslots'
+      ),
+      // Ki Points
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          totalPoints: 0,
+          usedPoints: 0,
+          showKiPoints: false,
+        },
+        'kiPoints'
+      ),
+
       // Character Appearance
-      this.dataService.addData(
+      this.dataAccessor.addData(
         this.characterName,
         {
           age: '',
@@ -111,7 +189,7 @@ export class CharacterCreatorComponent {
         'appearance'
       ),
       // Character Personality
-      this.dataService.addData(
+      this.dataAccessor.addData(
         this.characterName,
         {
           personalityTraits: '',
@@ -121,25 +199,140 @@ export class CharacterCreatorComponent {
         },
         'personality'
       ),
-      // Character Weapons
-      this.dataService.addData(
+      // Weapons
+      this.dataAccessor.addData(
         this.characterName,
         {
-          name: '',
-          attackBonus: '',
-          damage: '',
-          type: '',
-          range: '',
-          description: '',
+          data: [],
         },
-        'weapons'
+        'favoriteWeapons'
       ),
-
       // Inventory
-
-      // Weapons
-
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          data: [],
+        },
+        'weaponsAndArmor'
+      ),
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          data: [],
+        },
+        'miscellaneous'
+      ),
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          data: [],
+        },
+        'consumables'
+      ),
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          data: [],
+        },
+        'money'
+      ),
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          platinum: 0,
+          gold: 0,
+          electrum: 0,
+          silver: 0,
+          copper: 0,
+        },
+        'money'
+      ),
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          data: [],
+        },
+        'food'
+      ),
+      // Favorite Spells
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          spells: [],
+        },
+        'favoriteSpells'
+      ),
       // Spells
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          spells: [],
+        },
+        'spellLevel0'
+      ),
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          spells: [],
+        },
+        'spellLevel1'
+      ),
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          spells: [],
+        },
+        'spellLevel2'
+      ),
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          spells: [],
+        },
+        'spellLevel3'
+      ),
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          spells: [],
+        },
+        'spellLevel4'
+      ),
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          spells: [],
+        },
+        'spellLevel5'
+      ),
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          spells: [],
+        },
+        'spellLevel6'
+      ),
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          spells: [],
+        },
+        'spellLevel7'
+      ),
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          spells: [],
+        },
+        'spellLevel8'
+      ),
+      this.dataAccessor.addData(
+        this.characterName,
+        {
+          spells: [],
+        },
+        'spellLevel9'
+      ),
 
       // Notes
 

+ 2 - 1
src/app/character/character-picker/character-picker.component.ts

@@ -29,6 +29,7 @@ export class CharacterPickerComponent {
   }
 
   public selectCharacter(character: string) {
-    this.dataService.selectCharacter(character);
+    sessionStorage.setItem('characterName', character);
+    this.Router.navigate(['journal']);
   }
 }

+ 7 - 1
src/app/character/character.module.ts

@@ -5,6 +5,7 @@ import { CharacterRoutingModule } from './character-routing.module';
 import { CharacterPickerComponent } from './character-picker/character-picker.component';
 import { CharacterCreatorComponent } from './character-creator/character-creator.component';
 import { CharacterCardComponent } from './character-picker/character-card/character-card.component';
+import { MatSlideToggleModule } from '@angular/material/slide-toggle';
 
 @NgModule({
   declarations: [
@@ -12,6 +13,11 @@ import { CharacterCardComponent } from './character-picker/character-card/charac
     CharacterCreatorComponent,
     CharacterCardComponent,
   ],
-  imports: [CommonModule, CharacterRoutingModule, FormsModule],
+  imports: [
+    CommonModule,
+    CharacterRoutingModule,
+    FormsModule,
+    MatSlideToggleModule,
+  ],
 })
 export class CharacterModule {}

+ 3 - 1
src/app/journal/journal-home/journal-home.component.ts

@@ -1,6 +1,7 @@
 import { Component, ViewChild } from '@angular/core';
 import { NavigationPanelService } from 'src/services/navigationPanel/navigation-panel.service';
 import { Router } from '@angular/router';
+import { DataService } from 'src/services/data/data.service';
 
 @Component({
   selector: 'app-journal-home',
@@ -10,7 +11,8 @@ import { Router } from '@angular/router';
 export class JournalHomeComponent {
   public constructor(
     public navigation: NavigationPanelService,
-    private router: Router
+    private router: Router,
+    private dataAccessor: DataService
   ) {}
 
   @ViewChild('tabbar') tabbar: any;

+ 0 - 1
src/app/journal/journal-home/modal/modal.component.ts

@@ -17,7 +17,6 @@ export class ModalComponent {
   ngOnInit() {
     this.modalRef = document.getElementById('myModal');
     this.modalAccessor.modal$.subscribe((data) => {
-      console.log('modal component', data);
       this.modalComponent = data.component;
       this.inputData = data.data;
       this.open();

+ 3 - 3
src/app/journal/journal-home/navigation-panel/navigation-panel.component.html

@@ -34,7 +34,7 @@
       <div
         class="navigation-entry"
         (click)="setActiveProperty(4)"
-        [routerLink]="'./notes'"
+        [routerLink]="'./spellcards'"
       >
         Zauber
       </div>
@@ -43,7 +43,7 @@
       <div
         class="navigation-entry"
         (click)="setActiveProperty(5)"
-        [routerLink]="'./spellcards'"
+        [routerLink]="'./notes'"
       >
         Notizen
       </div>
@@ -107,7 +107,7 @@
   </ul>
 
   <div class="settings-container">
-    <button class="settings-button">
+    <button class="settings-button" [routerLink]="'../'">
       <icon [size]="'s'" [type]="'UI'" [icon]="'character'"></icon>
       <div>Charakterauswahl</div>
     </button>

+ 12 - 8
src/app/journal/journal-inventory/journal-inventory.component.ts

@@ -35,7 +35,7 @@ export class JournalInventoryComponent {
   public food: Food[] = [];
 
   public ngOnInit(): void {
-    this.weaponsAndArmor = this.dataAccessor.items;
+    this.weaponsAndArmor = this.dataAccessor.weaponsAndArmor;
     this.consumables = this.dataAccessor.consumables;
     this.miscellaneous = this.dataAccessor.miscellaneous;
     this.food = this.dataAccessor.food;
@@ -45,7 +45,7 @@ export class JournalInventoryComponent {
   drop(event: CdkDragDrop<string[]>, list: any[], listName: string) {
     moveItemInArray(list, event.previousIndex, event.currentIndex);
     if (listName === 'weaponsAndArmor') {
-      this.dataAccessor.items = list;
+      this.dataAccessor.weaponsAndArmor = list;
     } else if (listName === 'food') {
       this.dataAccessor.food = list;
     } else if (listName === 'consumables') {
@@ -63,12 +63,13 @@ export class JournalInventoryComponent {
     });
     const resultSubscription = this.detailsAccessor.result$.subscribe(
       (result) => {
-        // console.log('Result aus items-detail-panel:', result);
         if (result.state === 'delete') {
           list.splice(index, 1);
           this.updateDatabase(listName);
         } else if (result.state === 'update') {
           this.openItemModal(true, listName, list, index);
+        } else {
+          throw new Error('DND-Error: Unknown state: ' + result.state);
         }
         resultSubscription.unsubscribe();
       }
@@ -85,8 +86,9 @@ export class JournalInventoryComponent {
           this.food.splice(index, 1);
           this.dataAccessor.food = this.food;
         } else if (result.state === 'update') {
-          console.log('Details geschlossen mit aufruf zum update');
           this.openFoodModal(true, index);
+        } else {
+          throw new Error('DND-Error: Unknown state: ' + result.state);
         }
         resultSubscription.unsubscribe();
       }
@@ -104,13 +106,14 @@ export class JournalInventoryComponent {
     });
     const resultSubscription = this.modalAccessor.result$.subscribe(
       (result) => {
-        console.log('result kam zurück: ', result);
         if (result.state === 'update') {
           this.food[index!] = result.data;
           this.updateFood();
         } else if (result.state === 'add') {
           this.food.push(result.data);
           this.updateFood();
+        } else {
+          throw new Error('DND-Error: Unknown state: ' + result.state);
         }
         resultSubscription.unsubscribe();
       }
@@ -132,13 +135,14 @@ export class JournalInventoryComponent {
     });
     const resultSubscription = this.modalAccessor.result$.subscribe(
       (result) => {
-        console.log('Result vom Modal kam zurück:', result);
         if (result.state === 'update') {
           list![index!] = result.data;
           this.updateDatabase(listname);
         } else if (result.state === 'add') {
           list!.push(result.data);
           this.updateDatabase(listname);
+        } else {
+          throw new Error('DND-Error: Unknown state: ' + result.state);
         }
         resultSubscription.unsubscribe();
       }
@@ -165,13 +169,13 @@ export class JournalInventoryComponent {
 
   public updateDatabase(listname: string): void {
     if (listname === 'weaponsAndArmor') {
-      this.dataAccessor.items = this.weaponsAndArmor;
+      this.dataAccessor.weaponsAndArmor = this.weaponsAndArmor;
     } else if (listname === 'consumables') {
       this.dataAccessor.consumables = this.consumables;
     } else if (listname === 'miscellaneous') {
       this.dataAccessor.miscellaneous = this.miscellaneous;
     } else {
-      throw new Error('Unknown list name: ' + listname);
+      throw new Error('DND-ERROR: Unknown list name: ' + listname);
     }
   }
 

+ 0 - 5
src/app/journal/journal-inventory/simple-item-modal/simple-item-modal.component.ts

@@ -20,11 +20,6 @@ export class SimpleItemModalComponent {
   public isReady: boolean = false;
 
   public ngOnInit(): void {
-    console.log('ngOnInit() in simple-item-modal.component.ts');
-    console.log('isUpdate: ', this.isUpdate);
-    console.log('isFood: ', this.isFood);
-    console.log('item: ', this.item);
-
     if (this.isUpdate) {
       this.loadItem();
     }

+ 4 - 0
src/app/journal/journal-routing.module.ts

@@ -13,11 +13,15 @@ import { JournalPlacesComponent } from './journal-places/journal-places.componen
 import { JournalMapsComponent } from './journal-maps/journal-maps.component';
 import { JournalRulesetComponent } from './journal-ruleset/journal-ruleset.component';
 import { JournalSettingsComponent } from './journal-settings/journal-settings.component';
+import { DataResolverService } from 'src/services/dataResolve/data-resolver.service';
 
 const routes: Routes = [
   {
     path: '',
     component: JournalHomeComponent,
+    resolve: {
+      data: DataResolverService,
+    },
     children: [
       { path: 'stats', component: JournalStatsComponent },
       { path: 'character', component: JournalCharacterComponent },

+ 38 - 0
src/app/journal/journal-spellcards/add-card/add-card.component.html

@@ -0,0 +1,38 @@
+<div class="add-card">
+  @if(isModification == undefined) {
+  <icon
+    class="add"
+    [size]="'l'"
+    [type]="'UI'"
+    [icon]="'add'"
+    [class]="'pointer'"
+  ></icon>
+  <button
+    class="slide-button add-card-button"
+    (click)="continueToSpellSelection(false)"
+  >
+    Offiziellen Zauber auswählen
+  </button>
+  <button
+    class="slide-button add-card-button"
+    (click)="continueToSpellSelection(true)"
+  >
+    Offiziellen Zauber bearbeiten
+  </button>
+  <button class="slide-button add-card-button" (click)="emitNewSpell()">
+    Neuen Zauber erstellen
+  </button>
+  } @else {
+
+  <input
+    id="typeahead-basic"
+    type="text"
+    class="form-control"
+    (selectItem)="spellSelected($event.item); $event.preventDefault()"
+    [(ngModel)]="newSpellName"
+    [ngbTypeahead]="search"
+    placeholder="Name des Zaubers"
+  />
+  <button (click)="resetThis()">Abbrechen</button>
+  }
+</div>

+ 51 - 0
src/app/journal/journal-spellcards/add-card/add-card.component.scss

@@ -0,0 +1,51 @@
+.add-card {
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: space-around;
+    height: 14rem;
+    width: 10rem;
+    border: solid 1px var(--border-color);
+    border-radius: 10px;
+    // background-image: url("../../../../assets/images/spells/add-spell.jpg");
+    // background-size: auto 100%;
+    // background-position: center;
+    box-shadow: var(--shadow-small);
+    overflow: hidden;
+    transition: all 0.3s ease-in-out;
+
+    &:hover {
+        .slide-button {
+            transform: translateX(0);
+        }
+    }
+
+    .add {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+    }
+}
+
+.slide-button {
+    transform: translateX(-120%);
+    transition: transform 0.3s ease-in-out;
+}
+
+.add-card-button {
+    background-color: var(--primary-color);
+    border: 1px solid var(--border-color);
+    border-radius: 10px;
+    box-shadow: var(--shadow);
+    font-weight: 600;
+    width: 90%;
+    margin: 0 0.5rem;
+    transition: all 0.3s ease-in-out;
+
+    &:hover {
+        background-color: var(--primary-color-dark);
+        scale: 1.03;
+    }
+}

+ 23 - 0
src/app/journal/journal-spellcards/add-card/add-card.component.spec.ts

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

+ 76 - 0
src/app/journal/journal-spellcards/add-card/add-card.component.ts

@@ -0,0 +1,76 @@
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
+import { Observable, OperatorFunction } from 'rxjs';
+import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';
+import { FormsModule } from '@angular/forms';
+import { JsonPipe } from '@angular/common';
+import { SpellsService } from 'src/services/spells/spells.service';
+import { FormControl, ReactiveFormsModule } from '@angular/forms';
+
+@Component({
+  selector: 'add-card',
+  templateUrl: './add-card.component.html',
+  styleUrl: './add-card.component.scss',
+})
+export class AddCardComponent {
+  @Input() level!: number;
+
+  @Output() createNewSpell = new EventEmitter<any>();
+  @Output() onSpellSelected = new EventEmitter<any>();
+
+  public newSpellName: string = '';
+  private availableSpells: string[] = [];
+  public isModification: boolean | undefined;
+
+  public constructor(private spellsAccessor: SpellsService) {}
+
+  public ngOnInit(): void {
+    this.availableSpells = this.spellsAccessor.getListOfSpells(this.level);
+    this.spellsAccessor.closeSubject$.subscribe((level) => {
+      if (level !== this.level) {
+        this.resetThis();
+      }
+    });
+  }
+
+  // Enlarged view
+
+  public emitNewSpell(): void {
+    this.createNewSpell.emit(this.level);
+    this.resetThis();
+  }
+
+  public continueToSpellSelection(modify: boolean): void {
+    this.isModification = modify;
+    this.spellsAccessor.closeAllOthers(this.level);
+  }
+
+  public spellSelected(spellname: any): void {
+    const response = {
+      spell: this.spellsAccessor.getSpell(spellname),
+      isToModify: this.isModification,
+    };
+    this.onSpellSelected.emit(response);
+    this.resetThis();
+  }
+
+  public resetThis(): void {
+    this.newSpellName = '';
+    this.isModification = undefined;
+  }
+
+  public search: OperatorFunction<string, readonly string[]> = (
+    text$: Observable<string>
+  ) =>
+    text$.pipe(
+      debounceTime(200),
+      distinctUntilChanged(),
+      map((term) =>
+        term.length < 2
+          ? []
+          : this.availableSpells
+              .filter((v) => v.toLowerCase().indexOf(term.toLowerCase()) > -1)
+              .slice(0, 5)
+      )
+    );
+}

+ 29 - 230
src/app/journal/journal-spellcards/journal-spellcards.component.html

@@ -1,257 +1,56 @@
 <div class="spellcards-container">
   <div cdkDropListGroup>
-    <div class="example-container">
-      <h2>Zaubertricks</h2>
-      <button (click)="toggleSpellList(0)">Toggle Spell List</button>
-
-      <div
-        cdkDropList
-        cdkDropListOrientation="horizontal"
-        [cdkDropListData]="level0"
-        class="spell-list"
-        (cdkDropListDropped)="drop($event)"
-        [class]="showSpellList[0] ? '' : 'hidden'"
-      >
-        @for (spell of level0; let index = $index; track spell) {
-        <div class="spellcard" (click)="openModal(true, 0, index)" cdkDrag>
-          <div class="name">{{ spell.name }}</div>
-          <div>{{ spell.cost }}</div>
-          <div>{{ spell.level }}</div>
-          <div>{{ spell.school }}</div>
-        </div>
-        }
-        <div class="add-card" (click)="openModal(false, 0)">
-          <icon
-            class="add"
-            [size]="'l'"
-            [type]="'UI'"
-            [icon]="'add'"
-            [class]="'pointer'"
-          ></icon>
-        </div>
-      </div>
-    </div>
+    @for(level of [0,1,2,3,4,5,6,7,8,9]; track level; let index = $index) {
 
     <div class="example-container">
-      <h2>Level 1</h2>
-      <button (click)="toggleSpellList(1)">Toggle Spell List</button>
-
       <div
-        cdkDropList
-        cdkDropListOrientation="horizontal"
-        [cdkDropListData]="level1"
-        class="spell-list"
-        (cdkDropListDropped)="drop($event)"
-        [class]="showSpellList[1] ? '' : 'hidden'"
+        [class]="showSpellList[index] ? 'level-row' : 'level-row collapsed'"
+        (click)="toggleSpellList(index)"
       >
-        @for (item of level1; track item) {
-        <div class="spellcard" cdkDrag>
-          <div>{{ item.name }}</div>
-          <div>{{ item.cost }}</div>
-          <div>{{ item.level }}</div>
-          <div>{{ item.school }}</div>
-        </div>
-        }
-        <div class="add-card" (click)="openModal(false, 1)">
+        <div class="level-flex">
           <icon
-            class="add"
             [size]="'l'"
             [type]="'UI'"
-            [icon]="'add'"
+            [icon]="showSpellList[index] ? 'visible' : 'hidden'"
             [class]="'pointer'"
+            class="inline"
           ></icon>
+          <div class="heading-2 inline">{{ getSpellLevel(index) }}</div>
         </div>
       </div>
-    </div>
-
-    <div class="example-container">
-      <h2>Level 2</h2>
-      <button (click)="toggleSpellList(2)">Toggle Spell List</button>
-
-      <div
-        cdkDropList
-        cdkDropListOrientation="horizontal"
-        [cdkDropListData]="level2"
-        class="spell-list"
-        (cdkDropListDropped)="drop($event)"
-        [class]="showSpellList[2] ? '' : 'hidden'"
-      >
-        @for (item of level2; track item) {
-        <div class="spellcard" cdkDrag>
-          <div>{{ item.name }}</div>
-          <div>{{ item.cost }}</div>
-          <div>{{ item.level }}</div>
-          <div>{{ item.school }}</div>
-        </div>
-        }
-        <div class="add-card">
-          <icon
-            class="add"
-            [size]="'l'"
-            [type]="'UI'"
-            [icon]="'add'"
-            [class]="'pointer'"
-          ></icon>
-        </div>
-      </div>
-    </div>
-
-    <div class="example-container">
-      <h2>Level 3</h2>
-      <button (click)="toggleSpellList(3)">Toggle Spell List</button>
-
-      <div
-        cdkDropList
-        cdkDropListOrientation="horizontal"
-        [cdkDropListData]="level3"
-        class="spell-list"
-        (cdkDropListDropped)="drop($event)"
-        [class]="showSpellList[3] ? '' : 'hidden'"
-      >
-        @for (item of level3; track item) {
-        <div class="spellcard" cdkDrag>
-          <div>{{ item.name }}</div>
-          <div>{{ item.cost }}</div>
-          <div>{{ item.level }}</div>
-          <div>{{ item.school }}</div>
-        </div>
-        }
-        <div class="add-card">
-          <icon
-            class="add"
-            [size]="'l'"
-            [type]="'UI'"
-            [icon]="'add'"
-            [class]="'pointer'"
-          ></icon>
-        </div>
-      </div>
-    </div>
-
-    <div class="example-container">
-      <h2>Level 4</h2>
-
-      <div
-        cdkDropList
-        cdkDropListOrientation="horizontal"
-        [cdkDropListData]="level4"
-        class="spell-list"
-        (cdkDropListDropped)="drop($event)"
-      >
-        @for (item of level4; track item) {
-        <div class="spellcard" cdkDrag>
-          <div>{{ item.name }}</div>
-          <div>{{ item.cost }}</div>
-          <div>{{ item.level }}</div>
-          <div>{{ item.school }}</div>
-        </div>
-        }
-      </div>
-    </div>
-
-    <div class="example-container">
-      <h2>Level 5</h2>
 
       <div
         cdkDropList
         cdkDropListOrientation="horizontal"
-        [cdkDropListData]="level5"
+        [cdkDropListData]="getSpellList(index)"
         class="spell-list"
         (cdkDropListDropped)="drop($event)"
+        [class]="showSpellList[index] ? '' : 'hidden'"
       >
-        @for (item of level5; track item) {
-        <div class="spellcard" cdkDrag>
-          <div>{{ item.name }}</div>
-          <div>{{ item.cost }}</div>
-          <div>{{ item.level }}</div>
-          <div>{{ item.school }}</div>
+        @for (spell of getSpellList(index); let spellIndex = $index; track
+        spell) {
+        <spellcard
+          cdkDrag
+          [id]="spellIndex"
+          (cdkDragStarted)="dragStart(index)"
+          (cdkDragReleased)="dragEnd($event)"
+          [spell]="spell"
+          (click)="showFullSpellcard(spell, level, spellIndex)"
+        ></spellcard>
+        } @if (draggingIndex === index){
+        <div class="deletion-card" [id]="'deletion' + index">
+          Hier zum Löschen ablegen
         </div>
+        } @else {
+        <add-card
+          [level]="index"
+          (createNewSpell)="openSpellModal(false, index)"
+          (onSpellSelected)="handleSpellSelection($event, index)"
+        ></add-card>
         }
       </div>
     </div>
 
-    <div class="example-container">
-      <h2>Level 6</h2>
-
-      <div
-        cdkDropList
-        cdkDropListOrientation="horizontal"
-        [cdkDropListData]="level6"
-        class="spell-list"
-        (cdkDropListDropped)="drop($event)"
-      >
-        @for (item of level6; track item) {
-        <div class="spellcard" cdkDrag>
-          <div>{{ item.name }}</div>
-          <div>{{ item.cost }}</div>
-          <div>{{ item.level }}</div>
-          <div>{{ item.school }}</div>
-        </div>
-        }
-      </div>
-    </div>
-
-    <div class="example-container">
-      <h2>Level 7</h2>
-
-      <div
-        cdkDropList
-        cdkDropListOrientation="horizontal"
-        [cdkDropListData]="level7"
-        class="spell-list"
-        (cdkDropListDropped)="drop($event)"
-      >
-        @for (item of level7; track item) {
-        <div class="spellcard" cdkDrag>
-          <div>{{ item.name }}</div>
-          <div>{{ item.cost }}</div>
-          <div>{{ item.level }}</div>
-          <div>{{ item.school }}</div>
-        </div>
-        }
-      </div>
-    </div>
-
-    <div class="example-container">
-      <h2>Level 8</h2>
-
-      <div
-        cdkDropList
-        cdkDropListOrientation="horizontal"
-        [cdkDropListData]="level8"
-        class="spell-list"
-        (cdkDropListDropped)="drop($event)"
-      >
-        @for (item of level8; track item) {
-        <div class="spellcard" cdkDrag>
-          <div>{{ item.name }}</div>
-          <div>{{ item.cost }}</div>
-          <div>{{ item.level }}</div>
-          <div>{{ item.school }}</div>
-        </div>
-        }
-      </div>
-    </div>
-
-    <div class="example-container">
-      <h2>Level 9</h2>
-
-      <div
-        cdkDropList
-        cdkDropListOrientation="horizontal"
-        [cdkDropListData]="level9"
-        class="spell-list"
-        (cdkDropListDropped)="drop($event)"
-      >
-        @for (item of level9; track item) {
-        <div class="spellcard" cdkDrag>
-          <div>{{ item.name }}</div>
-          <div>{{ item.cost }}</div>
-          <div>{{ item.level }}</div>
-          <div>{{ item.school }}</div>
-        </div>
-        }
-      </div>
-    </div>
+    }
   </div>
 </div>

+ 101 - 43
src/app/journal/journal-spellcards/journal-spellcards.component.scss

@@ -3,87 +3,146 @@
   height: 88vh;
   padding: 2rem;
   overflow-y: auto;
+  display: flex;
+  flex-direction: column;
 }
 
-
 .example-container {
+  margin-bottom: 1rem;
+}
+
+.level-row {
+  width: 16rem;
+  gap: 1rem;
+  padding: 0.25rem 1rem;
+  background: white;
+  border: solid 1px var(--border-color);
+  border-bottom: none;
+  border-radius: 10px 10px 0 0;
+  cursor: pointer;
+  box-shadow: var(--shadow);
+
+  &.collapsed {
+    animation: borders 0.4s forwards;
+  }
+}
+
+@keyframes borders {
+  0% {
+    border: solid 1px var(--border-color);
+    border-radius: 10px 10px 0 0;
+  }
+  50% {
+    border: solid 1px var(--border-color);
+    border-radius: 10px 10px 0 0;
+  }
+  100% {
+    border: 1px solid var(--border-color);
+    border-radius: 10px;
+  }
+}
+
+.level-flex {
+  display: flex;
+  flex-direction: row;
+  gap: 1rem;
+  align-items: center;
+}
 
+.inline {
+  display: inline;
+}
+
+.flex {
+  display: flex;
+}
+
+.heading-2 {
+  margin: 0;
+  font-size: 1.75rem;
+  font-weight: 600;
+  line-height: 2rem;
 }
 
 .spell-list {
-  border: solid 1px #ccc;
+  border: solid 1px var(--border-color);
   height: 16rem;
-  width: 96%;
-  margin: 0 1rem;
   background: white;
-  border-radius: 4px;
-  overflow: hidden;
+  border-radius: 0 10px 10px 10px;
   display: flex;
   flex-direction: row;
   align-items: center;
   gap: 1rem;
   padding: 0 1rem;
   overflow-x: auto;
-  transition: height 0.4s ease-in-out;
+  overflow-y: hidden;
+  transition: height 0.3s ease-in-out;
+  box-shadow: var(--shadow);
 
-  &.hidden{
+  &.hidden {
     height: 0;
     overflow: hidden;
     transition: height 0.4s ease-in-out;
-    border:none;
+    border: none;
   }
 }
 
-@mixin card{
-  height: 14rem;
-  // width: 10rem;
-  flex: 0 0 10rem;
-  border: solid 1px var(--border-color);
-  border-radius: 10px;
-  background: white;
+.animate-visibility {
+  animation: icon-switch 0.5s ease-in-out;
 }
 
-.spellcard {
-  @include card;
-  cursor: move;
-
-  .name{
-    font-weight: 600;
-    font-size: 1.125rem;
-    text-align: center;
-    margin-top: 0.25rem;
-  }  
+@keyframes icon-switch {
+  0% {
+    opacity: 0;
+  }
+  50% {
+    opacity: 1;
+  }
+  100% {
+    opacity: 0;
+  }
 }
 
-.add-card{
-  @include card;
-  position: relative;
-  cursor: pointer;
+.deletion-card {
+  height: 14rem;
+  width: 10rem;
+  font-size: 1.25rem;
+  font-weight: 600;
+  border: solid 1px var(--border-color);
+  border-radius: 10px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  text-align: center;
+  background-color: var(--delete);
+  transition: scale 0.2s ease-in-out;
+  box-shadow: var(--shadow);
 
-  .add{
-    position: absolute;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);  
+  &:hover {
+    scale: 1.1;
   }
 }
 
+//  CDK DRAG
+
 .cdk-drag-preview {
   box-sizing: border-box;
-  border-radius: 4px;
-  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
-              0 8px 10px 1px rgba(0, 0, 0, 0.14),
-              0 3px 14px 2px rgba(0, 0, 0, 0.12);
+  border-radius: 10px;
+  box-shadow:
+    0 5px 5px -3px rgba(0, 0, 0, 0.2),
+    0 8px 10px 1px rgba(0, 0, 0, 0.14),
+    0 3px 14px 2px rgba(0, 0, 0, 0.12);
 }
 
 .cdk-drag-placeholder {
   background: #e0e0e0;
   border: dotted 3px #999;
+  border-radius: 10px;
   min-height: 60px;
   transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
-  font-size: 0 !important;
+  color: transparent !important;
 
-  .name{
+  .name {
     display: none;
   }
 }
@@ -92,7 +151,6 @@
   transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
 }
 
-
 .spell-list.cdk-drop-list-dragging .spellcard:not(.cdk-drag-placeholder) {
   transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
-}
+}

+ 263 - 45
src/app/journal/journal-spellcards/journal-spellcards.component.ts

@@ -10,7 +10,15 @@ import {
 import { Spell } from 'src/interfaces/spell';
 import { DataService } from 'src/services/data/data.service';
 import { ModalService } from 'src/services/modal/modal.service';
+import { SpellsService } from 'src/services/spells/spells.service';
 import { SpellModalComponent } from 'src/app/journal/spell-modal/spell-modal.component';
+import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
+import { Observable, OperatorFunction } from 'rxjs';
+import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';
+import { FormsModule } from '@angular/forms';
+import { JsonPipe } from '@angular/common';
+import { FormControl } from '@angular/forms';
+import { FullSpellcardComponent } from 'src/app/shared-components/full-spellcard/full-spellcard.component';
 
 @Component({
   selector: 'app-journal-spellcards',
@@ -18,13 +26,6 @@ import { SpellModalComponent } from 'src/app/journal/spell-modal/spell-modal.com
   styleUrls: ['./journal-spellcards.component.scss'],
 })
 export class JournalSpellcardsComponent {
-  public constructor(
-    public dataAccessor: DataService,
-    private modalAccessor: ModalService
-  ) {
-    this.loadSpells();
-  }
-
   public level0: Spell[] = [];
   public level1: Spell[] = [];
   public level2: Spell[] = [];
@@ -36,19 +37,6 @@ export class JournalSpellcardsComponent {
   public level8: Spell[] = [];
   public level9: Spell[] = [];
 
-  public listTranslator: any = {
-    0: this.level0,
-    1: this.level1,
-    2: this.level2,
-    3: this.level3,
-    4: this.level4,
-    5: this.level5,
-    6: this.level6,
-    7: this.level7,
-    8: this.level8,
-    9: this.level9,
-  };
-
   public showSpellList: boolean[] = [
     true,
     true,
@@ -62,18 +50,60 @@ export class JournalSpellcardsComponent {
     true,
   ];
 
+  public draggingIndex: number | undefined;
+
+  public constructor(
+    public dataAccessor: DataService,
+    private modalAccessor: ModalService
+  ) {
+    this.loadSpells();
+    this.hideEmptySpelllists();
+  }
+
   ///////// FUNCTIONS //////////
 
-  public openModal(
+  public showFullSpellcard(
+    spell: Spell,
+    level: number,
+    spellIndex: number
+  ): void {
+    const favorites = this.dataAccessor.favoriteSpells;
+    const alreadyInFavorites = favorites.some(
+      (currentSpell) => currentSpell.name === spell.name
+    );
+    this.modalAccessor.openModal(FullSpellcardComponent, {
+      spell: spell,
+      isFromDashboard: false,
+      alreadyInFavorites: alreadyInFavorites,
+    });
+    const resultSubscription = this.modalAccessor.result$.subscribe(
+      (result) => {
+        resultSubscription.unsubscribe();
+        if (result.state === 'delete') {
+          this.getSpellList(level).splice(spellIndex, 1);
+          this.updateSpellsInDatabase(level);
+        } else if (result.state === 'update') {
+          setTimeout(() => {
+            this.openSpellModal(true, level, spellIndex);
+          }, 100);
+        } else if (result.state === 'add') {
+          this.dataAccessor.addFavoriteSpell(spell);
+        } else if (result.state !== 'cancel') {
+          throw new Error('Unexpected result state, please send a bug report.');
+        }
+      }
+    );
+  }
+
+  public openSpellModal(
     isUpdate: boolean,
     level: number,
     spellIndex?: number
   ): void {
-    console.log(spellIndex);
     this.modalAccessor.openModal(SpellModalComponent, {
       spell:
         spellIndex !== undefined
-          ? JSON.parse(JSON.stringify(this.listTranslator[level][spellIndex]))
+          ? JSON.parse(JSON.stringify(this.getSpellList(level)![spellIndex]))
           : undefined,
       isUpdate: isUpdate,
       level: level,
@@ -81,10 +111,15 @@ export class JournalSpellcardsComponent {
     const resultSubscription = this.modalAccessor.result$.subscribe(
       (result) => {
         if (result.state === 'update') {
-          console.log(result.data);
-          this.updateSpell(result.data, level!, spellIndex!);
+          // level was not modified
+          if (level === result.data.level) {
+            this.updateSpell(result.data, level!, spellIndex!);
+          } else {
+            // level was modified
+            this.getSpellList(level).splice(spellIndex!, 1);
+            this.addSpell(result.data, result.data.level);
+          }
         } else if (result.state === 'add') {
-          console.log('im else: ', result.data);
           this.addSpell(result.data, level);
         }
         resultSubscription.unsubscribe();
@@ -92,33 +127,112 @@ export class JournalSpellcardsComponent {
     );
   }
 
+  public openSpellModalModifyOfficial(spell: Spell): void {
+    this.modalAccessor.openModal(SpellModalComponent, {
+      spell: spell,
+      isUpdate: true,
+      level: spell.level,
+    });
+    const resultSubscription = this.modalAccessor.result$.subscribe(
+      (result) => {
+        if (result.state === 'update') {
+          this.addSpell(result.data, result.data.level);
+        } else {
+          throw new Error('Unexpected result state, please send a bug report.');
+        }
+        resultSubscription.unsubscribe();
+      }
+    );
+  }
+
+  public handleSpellSelection(event: any, level: number): void {
+    const newSpell = event.spell;
+    const isToModify = event.isToModify;
+
+    if (isToModify) {
+      this.openSpellModalModifyOfficial(newSpell);
+    } else {
+      this.addSpell(newSpell, level);
+    }
+  }
+
   public addSpell(spell: Spell, level: number) {
-    console.log('The level: ', level);
-    console.log('The levellist:', this.listTranslator[level]);
-    console.log('The new spell: ', spell);
-    this.listTranslator[level].push(spell);
-    console.log(this.listTranslator[level]);
-    // this.updateSpellsInDatabase();
+    this.getSpellList(level).push(spell);
+    this.updateSpellsInDatabase(level);
   }
 
   public updateSpell(spell: Spell, level: number, index: number): void {
-    this.listTranslator[level][index] = spell;
-    // this.updateSpellsInDatabase();
+    this.getSpellList(level)![index] = spell;
+    this.updateSpellsInDatabase(level);
+  }
+
+  public getSpellList(level: number): Spell[] {
+    switch (level) {
+      case 0:
+        return this.level0;
+      case 1:
+        return this.level1;
+      case 2:
+        return this.level2;
+      case 3:
+        return this.level3;
+      case 4:
+        return this.level4;
+      case 5:
+        return this.level5;
+      case 6:
+        return this.level6;
+      case 7:
+        return this.level7;
+      case 8:
+        return this.level8;
+      case 9:
+        return this.level9;
+      default:
+        throw new Error('Invalid spell level');
+    }
+  }
+
+  public getSpellLevel(level: number): string {
+    switch (level) {
+      case 0:
+        return 'Zaubertricks';
+      case 1:
+        return 'Level 1';
+      case 2:
+        return 'Level 2';
+      case 3:
+        return 'Level 3';
+      case 4:
+        return 'Level 4';
+      case 5:
+        return 'Level 5';
+      case 6:
+        return 'Level 6';
+      case 7:
+        return 'Level 7';
+      case 8:
+        return 'Level 8';
+      case 9:
+        return 'Level 9';
+      default:
+        return '';
+    }
   }
 
-  toggleSpellList(index: number) {
-    console.log(index);
+  public toggleSpellList(index: number) {
     this.showSpellList[index] = !this.showSpellList[index];
   }
 
-  drop(event: CdkDragDrop<any[]>) {
+  public drop(event: CdkDragDrop<any[]>) {
     if (event.previousContainer === event.container) {
       moveItemInArray(
         event.container.data,
         event.previousIndex,
         event.currentIndex
       );
-      console.log(event.container.data);
+      this.updateSpellsInDatabase(this.getIndex(event.previousContainer.id));
+      // update database with one level
     } else {
       transferArrayItem(
         event.previousContainer.data,
@@ -126,55 +240,159 @@ export class JournalSpellcardsComponent {
         event.previousIndex,
         event.currentIndex
       );
+      this.updateSpellsInDatabase(this.getIndex(event.previousContainer.id));
 
+      // Update level of moved spell
       const movedSpell = event.container.data[event.currentIndex];
       const newContainer = event.container.id;
 
       switch (newContainer) {
         case 'cdk-drop-list-0':
           movedSpell.level = 0;
+          this.updateSpellsInDatabase(0);
           break;
         case 'cdk-drop-list-1':
           movedSpell.level = 1;
+          this.updateSpellsInDatabase(1);
           break;
         case 'cdk-drop-list-2':
           movedSpell.level = 2;
+          this.updateSpellsInDatabase(2);
           break;
         case 'cdk-drop-list-3':
           movedSpell.level = 3;
+          this.updateSpellsInDatabase(3);
           break;
         case 'cdk-drop-list-4':
           movedSpell.level = 4;
+          this.updateSpellsInDatabase(4);
           break;
         case 'cdk-drop-list-5':
           movedSpell.level = 5;
+          this.updateSpellsInDatabase(5);
           break;
         case 'cdk-drop-list-6':
           movedSpell.level = 6;
+          this.updateSpellsInDatabase(6);
           break;
         case 'cdk-drop-list-7':
           movedSpell.level = 7;
+          this.updateSpellsInDatabase(7);
           break;
         case 'cdk-drop-list-8':
           movedSpell.level = 8;
+          this.updateSpellsInDatabase(8);
           break;
         case 'cdk-drop-list-9':
           movedSpell.level = 9;
+          this.updateSpellsInDatabase(9);
           break;
       }
     }
   }
 
+  private getIndex(id: string): number {
+    switch (id) {
+      case 'cdk-drop-list-0':
+        return 0;
+      case 'cdk-drop-list-1':
+        return 1;
+      case 'cdk-drop-list-2':
+        return 2;
+      case 'cdk-drop-list-3':
+        return 3;
+      case 'cdk-drop-list-4':
+        return 4;
+      case 'cdk-drop-list-5':
+        return 5;
+      case 'cdk-drop-list-6':
+        return 6;
+      case 'cdk-drop-list-7':
+        return 7;
+      case 'cdk-drop-list-8':
+        return 8;
+      case 'cdk-drop-list-9':
+        return 9;
+      default:
+        throw new Error('DND-ERROR: Invalid spell level');
+    }
+  }
+
+  public dragStart(index: number) {
+    this.draggingIndex = index;
+  }
+
+  public dragEnd(event: any) {
+    if (event.event.target.classList.contains('deletion-card')) {
+      this.getSpellList(this.draggingIndex!).splice(
+        event.source.element.nativeElement.id,
+        1
+      );
+      this.updateSpellsInDatabase(this.draggingIndex!);
+    }
+    this.draggingIndex = undefined;
+  }
+
   private loadSpells(): void {
     this.level0 = this.dataAccessor.spellLevel0;
     this.level1 = this.dataAccessor.spellLevel1;
     this.level2 = this.dataAccessor.spellLevel2;
-    // this.level3 = this.dataAccessor.spellLevel3;
-    // this.level4 = this.dataAccessor.spellLevel4;
-    // this.level5 = this.dataAccessor.spellLevel5;
-    // this.level6 = this.dataAccessor.spellLevel6;
-    // this.level7 = this.dataAccessor.spellLevel7;
-    // this.level8 = this.dataAccessor.spellLevel8;
-    // this.level9 = this.dataAccessor.spellLevel9;
+    this.level3 = this.dataAccessor.spellLevel3;
+    this.level4 = this.dataAccessor.spellLevel4;
+    this.level5 = this.dataAccessor.spellLevel5;
+    this.level6 = this.dataAccessor.spellLevel6;
+    this.level7 = this.dataAccessor.spellLevel7;
+    this.level8 = this.dataAccessor.spellLevel8;
+    this.level9 = this.dataAccessor.spellLevel9;
+  }
+
+  private hideEmptySpelllists(): void {
+    this.showSpellList = [
+      this.level0.length > 0,
+      this.level1.length > 0,
+      this.level2.length > 0,
+      this.level3.length > 0,
+      this.level4.length > 0,
+      this.level5.length > 0,
+      this.level6.length > 0,
+      this.level7.length > 0,
+      this.level8.length > 0,
+      this.level9.length > 0,
+    ];
+  }
+
+  private updateSpellsInDatabase(level: number): void {
+    switch (level) {
+      case 0:
+        this.dataAccessor.spellLevel0 = this.level0;
+        break;
+      case 1:
+        this.dataAccessor.spellLevel1 = this.level1;
+        break;
+      case 2:
+        this.dataAccessor.spellLevel2 = this.level2;
+        break;
+      case 3:
+        this.dataAccessor.spellLevel3 = this.level3;
+        break;
+      case 4:
+        this.dataAccessor.spellLevel4 = this.level4;
+        break;
+      case 5:
+        this.dataAccessor.spellLevel5 = this.level5;
+        break;
+      case 6:
+        this.dataAccessor.spellLevel6 = this.level6;
+        break;
+      case 7:
+        this.dataAccessor.spellLevel7 = this.level7;
+        break;
+      case 8:
+        this.dataAccessor.spellLevel8 = this.level8;
+        break;
+      case 9:
+        this.dataAccessor.spellLevel9 = this.level9;
+        break;
+    }
   }
 }

+ 5 - 0
src/app/journal/journal-spellcards/spellcard/spellcard.component.html

@@ -0,0 +1,5 @@
+<div class="spellcard" #spellcard>
+  <div class="name">{{ spell.name }}</div>
+  <div>{{ spell.cost }}</div>
+  <div>{{ spell.school }}</div>
+</div>

+ 21 - 0
src/app/journal/journal-spellcards/spellcard/spellcard.component.scss

@@ -0,0 +1,21 @@
+.spellcard {
+    height: 14rem;
+    width: 10rem;
+    border: solid 1px var(--border-color);
+    border-radius: 10px;
+    background: white;
+    box-shadow: var(--shadow-small);
+    cursor: pointer;
+    transition: all 0.3s ease-in-out;
+
+    // &:hover {
+    //     box-shadow: var(--shadow-small);
+    // }
+
+    .name {
+        font-weight: 600;
+        font-size: 1.125rem;
+        text-align: center;
+        margin-top: 0.25rem;
+    }
+}

+ 23 - 0
src/app/journal/journal-spellcards/spellcard/spellcard.component.spec.ts

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

+ 15 - 0
src/app/journal/journal-spellcards/spellcard/spellcard.component.ts

@@ -0,0 +1,15 @@
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { Spell } from 'src/interfaces/spell';
+
+@Component({
+  selector: 'spellcard',
+  templateUrl: './spellcard.component.html',
+  styleUrl: './spellcard.component.scss',
+})
+export class SpellcardComponent {
+  @Input() public spell!: Spell;
+
+  public showLargeSpellcard(reference: any) {
+    reference.classList.add('large');
+  }
+}

+ 4 - 4
src/app/journal/journal-stats/ability-panel/ability-panel.component.ts

@@ -16,16 +16,16 @@ export class AbilityPanelComponent {
   public openModal() {
     switch (this.active) {
       case 1:
-        this.abilityTable.openAbilityModal();
+        this.abilityTable.openModal();
         break;
       case 2:
-        this.traitTable.openTraitModal();
+        this.traitTable.openModal();
         break;
       case 3:
-        this.spellslots.openSpellslotModal();
+        this.spellslots.openModal();
         break;
       case 4:
-        this.proficienciesTable.modifyToolsAndLanguages();
+        this.proficienciesTable.openModal();
         break;
     }
   }

+ 73 - 71
src/app/journal/journal-stats/ability-panel/ability-table/ability-modal/ability-modal.component.html

@@ -1,78 +1,80 @@
-<ngx-smart-modal
-  #abilityModal
-  identifier="abilityModal"
-  (onOpenFinished)="checkIfAbilityIsToUpdate()"
->
-  <div class="modal-title">
-    <h3 *ngIf="!isToUpdate">Fähigkeit erstellen</h3>
-    <h3 *ngIf="isToUpdate">Fähigkeit anpassen</h3>
-  </div>
-  <div class="modal-body">
-    <div class="modal-input">
-      <label for="abilityName">Name</label>
-      <input
-        type="text"
-        class="modal-input"
-        id="abilityName"
-        [(ngModel)]="newAbilityName"
-      />
+<div class="modal-dimensions">
+  <div class="add-form-group">
+    <div class="modal-title">
+      <h3 *ngIf="!isUpdate">Fähigkeit erstellen</h3>
+      <h3 *ngIf="isUpdate">Fähigkeit anpassen</h3>
     </div>
+    <div class="modal-body">
+      <div class="modal-input">
+        <label for="abilityName">Name</label>
+        <input
+          type="text"
+          class="modal-input"
+          id="abilityName"
+          [(ngModel)]="name"
+        />
+      </div>
 
-    <div class="modal-input">
-      <label for="abilityShortDescription">Kurzbeschreibung</label>
-      <textarea
-        id="abilityShortDescription"
-        [(ngModel)]="newAbilityShortDescription"
-      ></textarea>
-    </div>
+      <div class="modal-input">
+        <label for="abilityShortDescription">Kurzbeschreibung</label>
+        <textarea
+          id="abilityShortDescription"
+          [(ngModel)]="shortDescription"
+        ></textarea>
+      </div>
 
-    <div class="modal-input">
-      <label for="abilityLongDescription">Ausführliche Beschreibung</label>
-      <textarea
-        id="abilityLongDescription"
-        [(ngModel)]="newAbilityLongDescription"
-      ></textarea>
-    </div>
+      <div class="modal-input">
+        <label for="abilityLongDescription">Ausführliche Beschreibung</label>
+        <textarea
+          id="abilityLongDescription"
+          [(ngModel)]="longDescription"
+        ></textarea>
+      </div>
 
-    <div class="modal-input">
-      <label>Kosten</label>
-      <select [(ngModel)]="newAbilityCost">
-        <option *ngFor="let cost of costs" [value]="cost.value">
-          {{ cost.display }}
-        </option>
-      </select>
-    </div>
+      <div class="modal-input">
+        <label>Kosten</label>
+        <select [(ngModel)]="cost">
+          <option *ngFor="let cost of costs" [value]="cost.value">
+            {{ cost.display }}
+          </option>
+        </select>
+      </div>
 
-    <div class="modal-input">
-      <label>Verwendungen</label>
-      <select [(ngModel)]="newAbilityCharges">
-        <option *ngFor="let charge of charges" [value]="charge.value">
-          {{ charge.display }}
-        </option>
-      </select>
+      <div class="modal-input">
+        <label>Verwendungen</label>
+        <select [(ngModel)]="charges">
+          <option
+            *ngFor="let charge of chargesTranslator"
+            [value]="charge.value"
+          >
+            {{ charge.display }}
+          </option>
+        </select>
+      </div>
     </div>
   </div>
-  <button
-    *ngIf="!isToUpdate"
-    class="modal-button"
-    (click)="createAbility()"
-    [disabled]="
-      !newAbilityName ||
-      !newAbilityShortDescription ||
-      !newAbilityLongDescription
-    "
-  >
-    Fähigkeit hinzufügen
-  </button>
-  <button
-    *ngIf="isToUpdate"
-    (click)="updateAbility()"
-    [disabled]="
-      !newAbilityName ||
-      !newAbilityShortDescription ||
-      !newAbilityLongDescription
-    "
-  >
-    Fähigkeit aktualisieren
-  </button>
-</ngx-smart-modal>
+  <div class="button-wrapper-2-block">
+    @if(isUpdate){
+    <ui-button
+      [type]="'update'"
+      [size]="'xlarge'"
+      [color]="'primary'"
+      (click)="update()"
+    ></ui-button>
+    }@else{
+    <ui-button
+      *ngIf="!isUpdate"
+      [type]="'add'"
+      [size]="'xlarge'"
+      [color]="'primary'"
+      (click)="add()"
+    ></ui-button>
+    }
+    <ui-button
+      [type]="'dismiss'"
+      [size]="'xlarge'"
+      [color]="'primary'"
+      (click)="cancel()"
+    ></ui-button>
+  </div>
+</div>

+ 16 - 2
src/app/journal/journal-stats/ability-panel/ability-table/ability-modal/ability-modal.component.scss

@@ -1,5 +1,19 @@
-.modal-input{
+.modal-dimensions {
+    width: 40vw;
+    background-color: var(--modal-background);
+    border-radius: 10px;
+    border: 1px solid var(--border-color);
+    padding: 1rem;
+}
+
+.add-form-group {
+    display: flex;
+    flex-direction: column;
+    gap: 1rem;
+}
+
+.modal-input {
     display: flex;
     flex-direction: column;
     gap: 0.5rem;
-}
+}

+ 46 - 68
src/app/journal/journal-stats/ability-panel/ability-table/ability-modal/ability-modal.component.ts

@@ -1,5 +1,5 @@
-import { Component, Output, EventEmitter, Input } from '@angular/core';
-import { NgxSmartModalService } from 'ngx-smart-modal';
+import { Component, Input } from '@angular/core';
+import { ModalService } from 'src/services/modal/modal.service';
 import { Ability } from 'src/interfaces/ability';
 
 @Component({
@@ -8,26 +8,15 @@ import { Ability } from 'src/interfaces/ability';
   styleUrls: ['./ability-modal.component.scss'],
 })
 export class AbilityModalComponent {
-  public constructor(public ngxSmartModalService: NgxSmartModalService) {}
-
-  @Output() public abilityCreated: EventEmitter<Ability> =
-    new EventEmitter<Ability>();
-
-  @Output() public abilityUpdated: EventEmitter<any> = new EventEmitter<any>();
-
-  @Output() public abilityDelete: EventEmitter<number> =
-    new EventEmitter<number>();
-
-  @Input() public isToUpdate: boolean = false;
-  @Input() public abilityToUpdate: Ability | undefined;
-
-  public newAbilityName: string = '';
-  public newAbilityCharges: number = 0;
-  public newAbilityCurrentlyUsedCharges: number = 0;
-  public newAbilityCost: string = 'none';
-  public newAbilityShortDescription: string = '';
-  public newAbilityLongDescription: string = '';
+  @Input() public isUpdate: boolean = false;
+  @Input() public ability: Ability | undefined;
 
+  public name: string = '';
+  public charges: number = 0;
+  public currentlyUsedCharges: number = 0;
+  public cost: string = 'none';
+  public shortDescription: string = '';
+  public longDescription: string = '';
   public costs: any[] = [
     { display: 'keine', value: 'none' },
     { display: 'Aktion', value: 'action' },
@@ -35,7 +24,7 @@ export class AbilityModalComponent {
     { display: 'Reaktion', value: 'reaction' },
   ];
 
-  public charges: any[] = [
+  public chargesTranslator: any[] = [
     { display: 'unbegrenzt', value: 0 },
     { display: '1', value: 1 },
     { display: '2', value: 2 },
@@ -49,58 +38,47 @@ export class AbilityModalComponent {
     { display: '10', value: 10 },
   ];
 
-  public createAbility(): void {
-    const newAbility: Ability = {
-      name: this.newAbilityName,
-      shortDescription: this.newAbilityShortDescription,
-      longDescription: this.newAbilityLongDescription,
-      cost: this.newAbilityCost,
-      charges: this.newAbilityCharges,
-      currentlyUsedCharges: 0,
-    };
-    this.abilityCreated.emit(newAbility);
-    this.ngxSmartModalService.closeLatestModal();
-    this.resetModalData();
-  }
+  public constructor(private modalAccessor: ModalService) {}
 
-  public checkIfAbilityIsToUpdate(): void {
-    if (this.isToUpdate) {
-      this.newAbilityName = this.abilityToUpdate?.name || '';
-      this.newAbilityCharges = this.abilityToUpdate?.charges || 0;
-      this.newAbilityCurrentlyUsedCharges =
-        this.abilityToUpdate?.currentlyUsedCharges || 0;
-      this.newAbilityCost = this.abilityToUpdate?.cost || 'none';
-      this.newAbilityShortDescription =
-        this.abilityToUpdate?.shortDescription || '';
-      this.newAbilityLongDescription =
-        this.abilityToUpdate?.longDescription || '';
+  public ngOnInit(): void {
+    if (this.isUpdate) {
+      this.loadItem();
     }
   }
 
-  public updateAbility(): void {
-    const updatedAbility: Ability = {
-      name: this.newAbilityName,
-      shortDescription: this.newAbilityShortDescription,
-      longDescription: this.newAbilityLongDescription,
-      cost: this.newAbilityCost,
-      charges: this.newAbilityCharges,
-      currentlyUsedCharges: Math.min(
-        this.newAbilityCurrentlyUsedCharges,
-        this.newAbilityCharges
-      ),
+  //   FUNCTIONS
+
+  public loadItem(): void {
+    this.name = this.ability!.name;
+    this.charges = this.ability!.charges;
+    this.currentlyUsedCharges = this.ability!.currentlyUsedCharges;
+    this.cost = this.ability!.cost;
+    this.shortDescription = this.ability!.shortDescription;
+    this.longDescription = this.ability!.longDescription;
+  }
+
+  public createAbility(): Ability {
+    return {
+      name: this.name,
+      shortDescription: this.shortDescription,
+      longDescription: this.longDescription,
+      cost: this.cost,
+      charges: parseInt(this.charges.toString()),
+      currentlyUsedCharges: Math.min(this.currentlyUsedCharges, this.charges),
     };
-    this.abilityUpdated.emit(updatedAbility);
-    this.ngxSmartModalService.closeLatestModal();
-    this.resetModalData();
   }
 
-  private resetModalData(): void {
-    this.newAbilityName = '';
-    this.newAbilityShortDescription = '';
-    this.newAbilityLongDescription = '';
-    this.newAbilityCost = 'none';
-    this.newAbilityCharges = 0;
-    this.isToUpdate = false;
-    this.abilityToUpdate = undefined;
+  // RESPONSES
+
+  public cancel(): void {
+    this.modalAccessor.handleModalClosing('cancel', undefined);
+  }
+
+  public add(): void {
+    this.modalAccessor.handleModalClosing('add', this.createAbility());
+  }
+
+  public update(): void {
+    this.modalAccessor.handleModalClosing('update', this.createAbility());
   }
 }

+ 21 - 13
src/app/journal/journal-stats/ability-panel/ability-table/ability-table.component.html

@@ -1,9 +1,13 @@
 <div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
+  @for(ability of abilities; let index = $index; track ability){
   <div
     class="example-box"
-    [class]="ability.currentlyUsedCharges === ability.charges ? 'used' : ''"
-    *ngFor="let ability of abilities; let abilityIndex = index"
-    (click)="openDetailsPanel(abilityIndex)"
+    [class]="
+      ability.currentlyUsedCharges === ability.charges && ability.charges !== 0
+        ? 'used'
+        : ''
+    "
+    (click)="openDetailsPanel(index)"
     cdkDrag
   >
     <div *ngIf="ability.cost != 'none'" class="cost-box">
@@ -20,22 +24,26 @@
         *ngFor="let _ of getArray(ability.charges); let chargeIndex = index"
       >
         <input
-          [id]="'checkbox' + abilityIndex + '-' + chargeIndex"
+          [id]="'checkbox' + index + '-' + chargeIndex"
           type="checkbox"
           (click)="$event.stopPropagation()"
           (change)="
-            $event.stopPropagation();
-            handleChangedCharges(abilityIndex, $event.target)
+            $event.stopPropagation(); handleChangedCharges(index, $event.target)
           "
         />
       </span>
     </div>
   </div>
+  }@empty{
+  <div
+    style="
+      text-align: center;
+      margin-top: 2rem;
+      font-size: 1.25rem;
+      font-weight: 500;
+    "
+  >
+    Noch keine Fähigkeit hinzugefügt
+  </div>
+  }
 </div>
-
-<ability-modal
-  (abilityCreated)="addNewlyCreatedAbility($event)"
-  (abilityUpdated)="updateAbility($event)"
-  [isToUpdate]="isToUpdate"
-  [abilityToUpdate]="abilityToUpdate"
-></ability-modal>

+ 7 - 14
src/app/journal/journal-stats/ability-panel/ability-table/ability-table.component.scss

@@ -1,23 +1,16 @@
-.ability-name{
+.ability-name {
   font-size: 1.25rem;
   font-weight: 600;
 }
 
-.cost-info{
+.cost-info {
   position: absolute;
   left: 0;
   top: 0;
 }
 
-
-
-
-
-
-
-
 // Table
-.used{
+.used {
   opacity: 0.5;
 }
 
@@ -38,7 +31,6 @@
   display: flex;
   position: relative;
   flex-direction: column;
-  // align-items: center;
   justify-content: space-between;
   box-sizing: border-box;
   cursor: move;
@@ -49,9 +41,10 @@
 .cdk-drag-preview {
   box-sizing: border-box;
   border-radius: 4px;
-  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
-              0 8px 10px 1px rgba(0, 0, 0, 0.14),
-              0 3px 14px 2px rgba(0, 0, 0, 0.12);
+  box-shadow:
+    0 5px 5px -3px rgba(0, 0, 0, 0.2),
+    0 8px 10px 1px rgba(0, 0, 0, 0.14),
+    0 3px 14px 2px rgba(0, 0, 0, 0.12);
 }
 
 .cdk-drag-placeholder {

+ 46 - 38
src/app/journal/journal-stats/ability-panel/ability-table/ability-table.component.ts

@@ -7,9 +7,10 @@ import {
 } from '@angular/cdk/drag-drop';
 import { DataService } from 'src/services/data/data.service';
 import { Ability } from 'src/interfaces/ability';
-import { NgxSmartModalService } from 'ngx-smart-modal';
 import { AbilityDetailsComponent } from './ability-details/ability-details.component';
 import { DetailsService } from 'src/services/details/details.service';
+import { ModalService } from 'src/services/modal/modal.service';
+import { AbilityModalComponent } from './ability-modal/ability-modal.component';
 
 @Component({
   selector: 'ability-table',
@@ -29,16 +30,16 @@ export class AbilityTableComponent {
   public abilityToUpdate: Ability | undefined;
   public updateAbilityIndex: number | undefined;
 
-  ///////////
+  // LIFECYCLE
 
   public constructor(
     public dataAccessor: DataService,
-    public ngxSmartModalService: NgxSmartModalService,
-    private detailsAccessor: DetailsService
+    private detailsAccessor: DetailsService,
+    private modalAccessor: ModalService
   ) {}
 
   public ngOnInit(): void {
-    this.abilities = this.dataAccessor.getAbilities();
+    this.abilities = this.dataAccessor.abilities;
   }
 
   public ngAfterViewInit(): void {
@@ -47,24 +48,26 @@ export class AbilityTableComponent {
     });
   }
 
-  ///////////
+  // FUNCTIONS
 
-  public getArray(length: number): any[] {
-    return Array.from({ length: length });
-  }
-
-  public drop(event: CdkDragDrop<string[]>): void {
-    moveItemInArray(this.abilities, event.previousIndex, event.currentIndex);
-    this.updateDatabase();
-  }
-
-  public openAbilityModal(abilityIndex?: number): void {
-    if (abilityIndex !== undefined) {
-      this.isToUpdate = true;
-      this.abilityToUpdate = this.abilities[abilityIndex];
-      this.updateAbilityIndex = abilityIndex;
-    }
-    this.ngxSmartModalService.getModal('abilityModal').open();
+  public openModal(isUpdate: boolean, index?: number): void {
+    this.modalAccessor.openModal(AbilityModalComponent, {
+      ability:
+        index !== undefined
+          ? JSON.parse(JSON.stringify(this.abilities[index]))
+          : undefined,
+      isUpdate: isUpdate,
+    });
+    const resultSubscription = this.modalAccessor.result$.subscribe(
+      (result) => {
+        if (result.state === 'update') {
+          this.updateAbility(result.data, index!);
+        } else if (result.state === 'add') {
+          this.addAbility(result.data);
+        }
+        resultSubscription.unsubscribe();
+      }
+    );
   }
 
   public handleChangedCharges(abilityindex: number, CheckBoxRef: any): void {
@@ -113,35 +116,28 @@ export class AbilityTableComponent {
   }
 
   public updateDatabase(): void {
-    this.dataAccessor.setSAbilities(this.abilities);
+    this.dataAccessor.abilities = this.abilities;
   }
 
   // add
-  public addNewlyCreatedAbility(ability: Ability): void {
+  public addAbility(ability: Ability): void {
     this.abilities.push(ability);
     this.updateDatabase();
   }
 
   //  update
-  public updateAbility(ability: Ability): void {
-    this.abilities[this.updateAbilityIndex!] = ability;
+  public updateAbility(ability: Ability, index: number): void {
+    this.abilities[index] = ability;
     this.updateDatabase();
-    this.correctChargesView(this.updateAbilityIndex!);
-    this.resetUpdateData();
-  }
-
-  private resetUpdateData(): void {
-    this.isToUpdate = false;
-    this.abilityToUpdate = undefined;
-    this.updateAbilityIndex = undefined;
+    this.correctChargesView(index);
   }
 
+  // delete
   private deleteAbility(index: number): void {
     this.abilities.splice(index, 1);
     this.updateDatabase();
   }
-
-  // details panel
+  // DETAILS
 
   public openDetailsPanel(index: number): void {
     this.detailsAccessor.openPanel(AbilityDetailsComponent, {
@@ -149,14 +145,26 @@ export class AbilityTableComponent {
     });
     const resultSubscription = this.detailsAccessor.result$.subscribe(
       (result) => {
-        console.log(result);
         if (result.state === 'delete') {
           this.deleteAbility(index);
         } else if (result.state === 'update') {
-          this.openAbilityModal(index);
+          this.openModal(true, index);
+        } else {
+          throw new Error('DND-Error: Unknown state: ' + result.state);
         }
         resultSubscription.unsubscribe();
       }
     );
   }
+
+  // UTILITY
+
+  public getArray(length: number): any[] {
+    return Array.from({ length: length });
+  }
+
+  public drop(event: CdkDragDrop<string[]>): void {
+    moveItemInArray(this.abilities, event.previousIndex, event.currentIndex);
+    this.updateDatabase();
+  }
 }

+ 0 - 10
src/app/journal/journal-stats/ability-panel/proficiencies-table/proficiencies-table.component.html

@@ -80,14 +80,4 @@
       {{ language }}
     </div>
   </div>
-  <!-- <ui-button
-    style="margin: 1rem"
-    [type]="'edit'"
-    [size]="'xlarge'"
-    [color]="'primary'"
-    (click)="modifyToolsAndLanguages()"
-  >
-  </ui-button> -->
 </div>
-
-<tools-modal (proficienciesUpdated)="updateProficiencies($event)"></tools-modal>

+ 22 - 21
src/app/journal/journal-stats/ability-panel/proficiencies-table/proficiencies-table.component.ts

@@ -1,13 +1,8 @@
 import { Component } from '@angular/core';
-import {
-  CdkDragDrop,
-  CdkDropList,
-  CdkDrag,
-  moveItemInArray,
-} from '@angular/cdk/drag-drop';
+import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
 import { DataService } from 'src/services/data/data.service';
-import { Ability } from 'src/interfaces/ability';
-import { NgxSmartModalService } from 'ngx-smart-modal';
+import { ModalService } from 'src/services/modal/modal.service';
+import { ToolsModalComponent } from './tools-modal/tools-modal.component';
 
 @Component({
   selector: 'proficiencies-table',
@@ -15,17 +10,16 @@ import { NgxSmartModalService } from 'ngx-smart-modal';
   styleUrls: ['./proficiencies-table.component.scss'],
 })
 export class ProficienciesTableComponent {
+  public proficiencies!: any;
+
   public constructor(
     public dataAccessor: DataService,
-    public ngxSmartModalService: NgxSmartModalService
+    public modalAccessor: ModalService
   ) {
-    this.proficiencies = this.dataAccessor.getProficiencies();
+    // this.proficiencies = this.dataAccessor.proficiencies;
   }
-
-  public proficiencies!: any;
-
   public ngOnInit(): void {
-    // this.proficiencies = this.dataAccessor.getProficiencies();
+    this.proficiencies = this.dataAccessor.proficiencies;
   }
 
   public dropTools(event: CdkDragDrop<string[]>): void {
@@ -58,21 +52,28 @@ export class ProficienciesTableComponent {
     }
   }
 
-  public modifyToolsAndLanguages(): void {
-    this.ngxSmartModalService.setModalData(
-      this.proficiencies,
-      'toolsAndLanguagesModal'
+  public openModal(): void {
+    this.modalAccessor.openModal(ToolsModalComponent, {
+      proficiencies: JSON.parse(JSON.stringify(this.proficiencies)),
+    });
+    const resultSubscription = this.modalAccessor.result$.subscribe(
+      (result) => {
+        if (result.state === 'update') {
+          this.updateProficiencies(result.data);
+        } else {
+          throw new Error('DND-Error: Invalid result state');
+        }
+        resultSubscription.unsubscribe();
+      }
     );
-    this.ngxSmartModalService.getModal('toolsAndLanguagesModal').open();
   }
 
   public updateDatabase(): void {
-    this.dataAccessor.setProficiencies(this.proficiencies);
+    this.dataAccessor.proficiencies = this.proficiencies;
   }
 
   public updateProficiencies(data: any): void {
     this.proficiencies = data;
-    this.ngxSmartModalService.getModal('toolsAndLanguagesModal').close();
     this.updateDatabase();
   }
 }

+ 20 - 7
src/app/journal/journal-stats/ability-panel/proficiencies-table/tools-modal/tools-modal.component.html

@@ -1,8 +1,8 @@
-<ngx-smart-modal
-  #toolsAndLanguagesModal
-  identifier="toolsAndLanguagesModal"
-  (onOpenFinished)="loadData()"
->
+<div class="modal-dimensions">
+  <h1 style="text-align: center">Hinweis</h1>
+  <p style="text-align: center">
+    Diese Seite ist momentan nur eingeschränkt funktionsbereit.
+  </p>
   <h4>Werkzeuge</h4>
   <div style="display: flex; flex-direction: column; gap: 0.5rem">
     <ng-container
@@ -59,5 +59,18 @@
     ></icon>
   </div>
 
-  <button (click)="updateProficiencies()">Aktualisieren</button>
-</ngx-smart-modal>
+  <div class="button-wrapper-2-block">
+    <ui-button
+      [type]="'update'"
+      [size]="'xlarge'"
+      [color]="'primary'"
+      (click)="update()"
+    ></ui-button>
+    <ui-button
+      [type]="'dismiss'"
+      [size]="'xlarge'"
+      [color]="'primary'"
+      (click)="cancel()"
+    ></ui-button>
+  </div>
+</div>

+ 14 - 5
src/app/journal/journal-stats/ability-panel/proficiencies-table/tools-modal/tools-modal.component.scss

@@ -1,4 +1,12 @@
-// 
+.modal-dimensions {
+  width: 40vw;
+  background-color: var(--modal-background);
+  border-radius: 10px;
+  border: 1px solid var(--border-color);
+  padding: 1rem;
+}
+
+//
 .example-list {
   width: 100%;
   border: solid 1px #ccc;
@@ -27,9 +35,10 @@
 .cdk-drag-preview {
   box-sizing: border-box;
   border-radius: 4px;
-  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
-              0 8px 10px 1px rgba(0, 0, 0, 0.14),
-              0 3px 14px 2px rgba(0, 0, 0, 0.12);
+  box-shadow:
+    0 5px 5px -3px rgba(0, 0, 0, 0.2),
+    0 8px 10px 1px rgba(0, 0, 0, 0.14),
+    0 3px 14px 2px rgba(0, 0, 0, 0.12);
 }
 
 .cdk-drag-placeholder {
@@ -46,4 +55,4 @@
 
 .example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
   transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
-}
+}

+ 16 - 27
src/app/journal/journal-stats/ability-panel/proficiencies-table/tools-modal/tools-modal.component.ts

@@ -1,11 +1,6 @@
-import { Component, Input, Output, EventEmitter } from '@angular/core';
-import {
-  CdkDragDrop,
-  CdkDropList,
-  CdkDrag,
-  moveItemInArray,
-} from '@angular/cdk/drag-drop';
-import { NgxSmartModalService } from 'ngx-smart-modal';
+import { Component, Input } from '@angular/core';
+import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
+import { ModalService } from 'src/services/modal/modal.service';
 
 @Component({
   selector: 'tools-modal',
@@ -13,20 +8,16 @@ import { NgxSmartModalService } from 'ngx-smart-modal';
   styleUrls: ['./tools-modal.component.scss'],
 })
 export class ToolsModalComponent {
-  public constructor(public ngxSmartModalService: NgxSmartModalService) {}
+  @Input() public proficiencies: any = { tools: [], languages: [] };
 
-  public proficiencies: any = { tools: [], languages: [] };
-  @Output() public proficienciesUpdated = new EventEmitter<any>();
+  public tools: any;
+  public languages: any;
 
-  public loadData(): void {
-    this.proficiencies = JSON.parse(
-      JSON.stringify(
-        this.ngxSmartModalService.getModalData('toolsAndLanguagesModal')
-      )
-    );
-    this.ngxSmartModalService.resetModalData('toolsAndLanguagesModal');
+  public constructor(public modalAccessor: ModalService) {}
 
-    console.log('Proficiencies: ', this.proficiencies);
+  ngOnInit(): void {
+    this.tools = JSON.parse(JSON.stringify(this.proficiencies.tools));
+    this.languages = JSON.parse(JSON.stringify(this.proficiencies.languages));
   }
 
   public dropTools(event: CdkDragDrop<string[]>): void {
@@ -36,7 +27,6 @@ export class ToolsModalComponent {
       event.currentIndex
     );
   }
-
   public dropLanguages(event: CdkDragDrop<string[]>): void {
     moveItemInArray(
       this.proficiencies.languages,
@@ -44,24 +34,23 @@ export class ToolsModalComponent {
       event.currentIndex
     );
   }
-
   public addTool(): void {
     this.proficiencies.tools.push('');
   }
-
   public deleteTool(index: number): void {
     this.proficiencies.tools.splice(index, 1);
   }
-
   public addLanguage(): void {
     this.proficiencies.languages.push('');
   }
-
   public deleteLanguage(index: number): void {
     this.proficiencies.languages.splice(index, 1);
   }
-
-  public updateProficiencies(): void {
-    this.proficienciesUpdated.emit(this.proficiencies);
+  // RESPONSES
+  public cancel(): void {
+    this.modalAccessor.handleModalClosing('cancel', undefined);
+  }
+  public update(): void {
+    this.modalAccessor.handleModalClosing('update', this.proficiencies);
   }
 }

+ 63 - 50
src/app/journal/journal-stats/ability-panel/spellslots/spellslots-modal/spellslots-modal.component.html

@@ -1,57 +1,70 @@
-<ngx-smart-modal #spellslotsModal identifier="spellslotsModal">
-  <h3>Spellslots</h3>
-  <switch
-    [size]="'s'"
-    [checked]="showSpellslots"
-    (change)="onSpellslotsSwitchChanged($event)"
-  ></switch>
-  Zauberplätze in der Übersicht anzeigen
-  <div *ngIf="showSpellslots">
-    <div *ngFor="let level of spellslots; let levelIndex = index">
-      <div class="level-row">
-        Level {{ levelIndex + 1 }}
-        <select [(ngModel)]="spellslots[levelIndex].totalSlots">
-          <option *ngFor="let number of spellNumbersArray" [value]="number">
+<div class="modal-dimensions">
+  <div class="add-form-group">
+    <h3>Spellslots</h3>
+    <mat-slide-toggle
+      [checked]="showSpellslots"
+      (change)="onSpellslotsSwitchChanged($event)"
+      >Zauberplätze in der Übersicht anzeigen</mat-slide-toggle
+    >
+    <!-- Zauberplätze in der Übersicht anzeigen -->
+    <div *ngIf="showSpellslots">
+      <div *ngFor="let level of spellslots; let levelIndex = index">
+        <div class="level-row">
+          Level {{ levelIndex + 1 }}
+          <select [(ngModel)]="spellslots[levelIndex].totalSlots">
+            <option *ngFor="let number of spellNumbersArray" [value]="number">
+              {{ number }}
+            </option>
+          </select>
+          <span>
+            <icon
+              [icon]="'remove'"
+              [size]="'s'"
+              [type]="'UI'"
+              [class]="'pointer'"
+              (click)="removeSpellLevel(levelIndex)"
+            ></icon>
+          </span>
+        </div>
+      </div>
+      <icon
+        [icon]="'add'"
+        [size]="'s'"
+        [type]="'UI'"
+        [class]="'pointer'"
+        (click)="addSpellLevel()"
+      ></icon>
+    </div>
+
+    <h3>KI-Punkte</h3>
+    <mat-slide-toggle
+      [checked]="kiPoints.showKiPoints"
+      (change)="onKiPointsSwitchChanged($event)"
+      >KI Punkte in der Übersicht anzeigen
+    </mat-slide-toggle>
+    <div *ngIf="kiPoints.showKiPoints">
+      Verfügbare KI Punkte
+      <div>
+        <select [(ngModel)]="kiPoints.totalPoints">
+          <option *ngFor="let number of kiNumbersArray" [value]="number">
             {{ number }}
           </option>
         </select>
-        <span>
-          <icon
-            [icon]="'remove'"
-            [size]="'s'"
-            [type]="'UI'"
-            [class]="'pointer'"
-            (click)="removeSpellLevel(levelIndex)"
-          ></icon>
-        </span>
       </div>
     </div>
-    <icon
-      [icon]="'add'"
-      [size]="'s'"
-      [type]="'UI'"
-      [class]="'pointer'"
-      (click)="addSpellLevel()"
-    ></icon>
   </div>
-
-  <h3>KI-Punkte</h3>
-  <switch
-    [size]="'s'"
-    [checked]="kiPoints.showKiPoints"
-    (change)="onKiPointsSwitchChanged($event)"
-  ></switch
-  >KI Punkte in der Übersicht anzeigen
-  <div *ngIf="kiPoints.showKiPoints">
-    Verfügbare KI Punkte
-    <div>
-      <select [(ngModel)]="kiPoints.totalPoints">
-        <option *ngFor="let number of kiNumbersArray" [value]="number">
-          {{ number }}
-        </option>
-      </select>
-    </div>
+  <div class="button-wrapper-2-block">
+    <ui-button
+      [type]="'update'"
+      [size]="'xlarge'"
+      [color]="'primary'"
+      (click)="update()"
+    ></ui-button>
+    <ui-button
+      [type]="'dismiss'"
+      [size]="'xlarge'"
+      [color]="'primary'"
+      (click)="cancel()"
+    ></ui-button>
   </div>
-  <button (click)="updateSlotsandPoints()">Aktualisieren</button>
-  <button (click)="abortUpdate()">Verwerfen</button>
-</ngx-smart-modal>
+</div>

+ 13 - 0
src/app/journal/journal-stats/ability-panel/spellslots/spellslots-modal/spellslots-modal.component.scss

@@ -0,0 +1,13 @@
+.modal-dimensions {
+    width: 40vw;
+    background-color: var(--modal-background);
+    border-radius: 10px;
+    border: 1px solid var(--border-color);
+    padding: 1rem;
+}
+
+.add-form-group {
+    display: flex;
+    flex-direction: column;
+    gap: 1rem;
+}

+ 33 - 26
src/app/journal/journal-stats/ability-panel/spellslots/spellslots-modal/spellslots-modal.component.ts

@@ -1,5 +1,6 @@
-import { Component, Input, Output, EventEmitter } from '@angular/core';
-import { NgxSmartModalService } from 'ngx-smart-modal';
+import { Component, Input } from '@angular/core';
+import { ModalService } from 'src/services/modal/modal.service';
+import { MatSlideToggleModule } from '@angular/material/slide-toggle';
 
 @Component({
   selector: 'spellslots-modal',
@@ -7,33 +8,25 @@ import { NgxSmartModalService } from 'ngx-smart-modal';
   styleUrls: ['./spellslots-modal.component.scss'],
 })
 export class SpellslotsModalComponent {
-  public constructor(public ngxSmartModalService: NgxSmartModalService) {}
-
   @Input() public spellslots: any[] = [];
   @Input() public kiPoints: any;
   @Input() public showSpellslots: boolean = true;
 
-  @Output() public slotsAndPointsUpdated = new EventEmitter<any>();
-
   public spellNumbersArray: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
   public kiNumbersArray: number[] = [
     1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
   ];
 
-  ngOnInit(): void {
-    // Create a deep copy of the spellslots and kiPoints objects so they are not modified in the parent component
-    this.kiPoints = JSON.parse(JSON.stringify(this.kiPoints));
-    this.spellslots = JSON.parse(JSON.stringify(this.spellslots));
-  }
+  public constructor(public modalAccessor: ModalService) {}
 
   // Shows the spellslot container
   public onSpellslotsSwitchChanged(event: any): void {
-    this.showSpellslots = event.target.checked;
+    this.showSpellslots = event.checked;
   }
 
   // Shows the ki points container
   public onKiPointsSwitchChanged(event: any): void {
-    this.kiPoints.showKiPoints = event.target.checked;
+    this.kiPoints.showKiPoints = event.checked;
   }
 
   // Removes the specified spellslot level
@@ -49,28 +42,42 @@ export class SpellslotsModalComponent {
     });
   }
 
-  public updateSlotsandPoints(): void {
+  public updateSlotsandPoints(): any {
     this.spellslots.forEach((level) => {
-      if (level.usedSlots > level.totalSlots) {
-        level.usedSlots = level.totalSlots;
-      }
+      level.usedSlots = Math.min(level.usedSlots, level.totalSlots);
     });
-    if (this.kiPoints.usedPoints > this.kiPoints.totalPoints) {
-      this.kiPoints.usedPoints = this.kiPoints.totalPoints;
-    }
-    const updatedSlotsAndPoints = {
+    this.kiPoints.usedPoints = Math.min(
+      this.kiPoints.usedPoints,
+      this.kiPoints.totalPoints
+    );
+    const updatedSlotsAndPoints: any = {
       spellslots: {
         spellslots: this.spellslots,
         showSpellslots: this.showSpellslots,
       },
-      kiPoints: { kiPoints: this.kiPoints },
+      kiPoints: this.kiPoints,
+    };
+    return updatedSlotsAndPoints;
+  }
+
+  private resultData(): any {
+    return {
+      spellslots: this.spellslots,
+      kiPoints: this.kiPoints,
+      showSpellslots: this.showSpellslots,
     };
+  }
+
+  // RESPONSES
 
-    this.slotsAndPointsUpdated.emit(updatedSlotsAndPoints);
-    this.ngxSmartModalService.close('spellslotsModal');
+  public cancel(): void {
+    this.modalAccessor.handleModalClosing('cancel', undefined);
   }
 
-  public abortUpdate(): void {
-    this.ngxSmartModalService.close('spellslotsModal');
+  public update(): void {
+    this.modalAccessor.handleModalClosing(
+      'update',
+      this.updateSlotsandPoints()
+    );
   }
 }

+ 22 - 8
src/app/journal/journal-stats/ability-panel/spellslots/spellslots.component.html

@@ -30,12 +30,26 @@
       </ng-container>
     </div>
   </div>
+  @if(!kiPoints.showKiPoints && !showSpellslots){
+  <div
+    style="
+      text-align: center;
+      margin-top: 2rem;
+      font-size: 1.25rem;
+      font-weight: 500;
+    "
+  >
+    Weder KI-Punkte noch Zauberplätze hinzugefügt
+  </div>
+  }
+  <div style="display: flex; justify-content: space-around; margin-top: 2rem">
+    <div>
+      <div class="value-box">14</div>
+      <div class="value-label">Rettungswurf-SG</div>
+    </div>
+    <div>
+      <div class="value-box">+6</div>
+      <div class="value-label">Zauber-Angriffsbonus</div>
+    </div>
+  </div>
 </div>
-
-<spellslots-modal
-  [kiPoints]="kiPoints"
-  [spellslots]="spellslots"
-  [showSpellslots]="showSpellslots"
-  (slotsAndPointsUpdated)="updateSlotsAndPoints($event)"
->
-</spellslots-modal>

+ 33 - 19
src/app/journal/journal-stats/ability-panel/spellslots/spellslots.component.ts

@@ -1,6 +1,7 @@
 import { Component } from '@angular/core';
 import { DataService } from 'src/services/data/data.service';
-import { NgxSmartModalService } from 'ngx-smart-modal';
+import { ModalService } from 'src/services/modal/modal.service';
+import { SpellslotsModalComponent } from './spellslots-modal/spellslots-modal.component';
 
 @Component({
   selector: 'spellslots',
@@ -8,11 +9,6 @@ import { NgxSmartModalService } from 'ngx-smart-modal';
   styleUrls: ['./spellslots.component.scss'],
 })
 export class SpellslotsComponent {
-  public constructor(
-    public dataAccessor: DataService,
-    public ngxSmartModalService: NgxSmartModalService
-  ) {}
-
   public spellslots: any[] = [];
   public showSpellslots: boolean = false;
   public kiPoints: any;
@@ -20,8 +16,8 @@ export class SpellslotsComponent {
   public slotNumber: number = 1;
 
   public ngOnInit(): void {
-    const spells = this.dataAccessor.getSpellslots();
-    const kiPoints = this.dataAccessor.getKiPoints();
+    const spells = this.dataAccessor.spellslots;
+    const kiPoints = this.dataAccessor.kiPoints;
     this.spellslots = spells.spellslots;
     this.showSpellslots = spells.showSpellslots;
     this.kiPoints = kiPoints;
@@ -33,10 +29,33 @@ export class SpellslotsComponent {
         this.correctSpellslotsView(levelIndex);
       });
       this.correctKiPointsView();
-    }, 200);
+    }, 10);
   }
 
-  //////////////
+  public constructor(
+    public dataAccessor: DataService,
+    public modalAccessor: ModalService
+  ) {}
+
+  // FUNCTIONS
+
+  public openModal(): void {
+    this.modalAccessor.openModal(SpellslotsModalComponent, {
+      kiPoints: JSON.parse(JSON.stringify(this.kiPoints)),
+      spellslots: JSON.parse(JSON.stringify(this.spellslots)),
+      showSpellslots: this.showSpellslots,
+    });
+    const resultSubscription = this.modalAccessor.result$.subscribe(
+      (result) => {
+        if (result.state === 'update') {
+          this.updateSlotsAndPoints(result.data);
+        } else {
+          throw new Error('DND-ERROR: Invalid state');
+        }
+        resultSubscription.unsubscribe();
+      }
+    );
+  }
 
   // ki points
 
@@ -130,25 +149,20 @@ export class SpellslotsComponent {
   }
 
   public updateSpellslotDatabase(): void {
-    this.dataAccessor.setSpellslots({
+    this.dataAccessor.spellslots = {
       spellslots: this.spellslots,
       showSpellslots: this.showSpellslots,
-    });
+    };
   }
 
   public updateKiPointsDatabase(): void {
-    this.dataAccessor.setKiPoints(this.kiPoints);
-  }
-
-  public openSpellslotModal(): void {
-    this.ngxSmartModalService.getModal('spellslotsModal').open();
+    this.dataAccessor.kiPoints = this.kiPoints;
   }
 
   public updateSlotsAndPoints(data: any): void {
-    console.log(data.spellslots.spellslots);
     this.spellslots = data.spellslots.spellslots;
     this.showSpellslots = data.spellslots.showSpellslots;
-    this.kiPoints = data.kiPoints.kiPoints;
+    this.kiPoints = data.kiPoints;
 
     setTimeout(() => {
       this.spellslots.forEach((_, levelIndex) => {

+ 54 - 51
src/app/journal/journal-stats/ability-panel/trait-table/trait-modal/trait-modal.component.html

@@ -1,56 +1,59 @@
-<ngx-smart-modal
-  #traitModal
-  identifier="traitModal"
-  (onOpenFinished)="checkIfTraitIsToUpdate()"
->
-  <div class="modal-title">
-    <h3 *ngIf="!isToUpdate">Eigenschaft erstellen</h3>
-    <h3 *ngIf="isToUpdate">Eigenschaft anpassen</h3>
-  </div>
-  <div class="modal-body">
-    <div class="modal-input">
-      <label for="traitName">Name</label>
-      <input
-        type="text"
-        class="modal-input"
-        id="traitName"
-        [(ngModel)]="newTraitName"
-      />
+<div class="modal-dimensions">
+  <div class="add-form-group">
+    <div class="modal-title">
+      <h3 *ngIf="!isUpdate">Eigenschaft erstellen</h3>
+      <h3 *ngIf="isUpdate">Eigenschaft anpassen</h3>
     </div>
+    <div class="modal-body">
+      <div class="modal-input">
+        <label for="traitName">Name</label>
+        <input
+          type="text"
+          class="modal-input"
+          id="traitName"
+          [(ngModel)]="name"
+        />
+      </div>
 
-    <div class="modal-input">
-      <label for="traitShortDescription">Kurzbeschreibung</label>
-      <textarea
-        id="traitShortDescription"
-        [(ngModel)]="newTraitShortDescription"
-      ></textarea>
-    </div>
+      <div class="modal-input">
+        <label for="traitShortDescription">Kurzbeschreibung</label>
+        <textarea
+          id="traitShortDescription"
+          [(ngModel)]="shortDescription"
+        ></textarea>
+      </div>
 
-    <div class="modal-input">
-      <label for="traitLongDescription">Ausführliche Beschreibung</label>
-      <textarea
-        id="traitLongDescription"
-        [(ngModel)]="newTraitLongDescription"
-      ></textarea>
+      <div class="modal-input">
+        <label for="traitLongDescription">Ausführliche Beschreibung</label>
+        <textarea
+          id="traitLongDescription"
+          [(ngModel)]="longDescription"
+        ></textarea>
+      </div>
     </div>
   </div>
-  <button
-    *ngIf="!isToUpdate"
-    class="modal-button"
-    (click)="createTrait()"
-    [disabled]="
-      !newTraitName || !newTraitShortDescription || !newTraitLongDescription
-    "
-  >
-    Eigenschaft hinzufügen
-  </button>
-  <button
-    *ngIf="isToUpdate"
-    (click)="updateTrait()"
-    [disabled]="
-      !newTraitName || !newTraitShortDescription || !newTraitLongDescription
-    "
-  >
-    Eigenschaft aktualisieren
-  </button>
-</ngx-smart-modal>
+  <div class="button-wrapper-2-block">
+    @if(isUpdate){
+    <ui-button
+      [type]="'update'"
+      [size]="'xlarge'"
+      [color]="'primary'"
+      (click)="update()"
+    ></ui-button>
+    }@else{
+    <ui-button
+      *ngIf="!isUpdate"
+      [type]="'add'"
+      [size]="'xlarge'"
+      [color]="'primary'"
+      (click)="add()"
+    ></ui-button>
+    }
+    <ui-button
+      [type]="'dismiss'"
+      [size]="'xlarge'"
+      [color]="'primary'"
+      (click)="cancel()"
+    ></ui-button>
+  </div>
+</div>

+ 16 - 2
src/app/journal/journal-stats/ability-panel/trait-table/trait-modal/trait-modal.component.scss

@@ -1,5 +1,19 @@
-.modal-input{
+.modal-input {
     display: flex;
     flex-direction: column;
     gap: 0.5rem;
-}
+}
+
+.modal-dimensions {
+    width: 40vw;
+    background-color: var(--modal-background);
+    border-radius: 10px;
+    border: 1px solid var(--border-color);
+    padding: 1rem;
+}
+
+.add-form-group {
+    display: flex;
+    flex-direction: column;
+    gap: 1rem;
+}

+ 34 - 46
src/app/journal/journal-stats/ability-panel/trait-table/trait-modal/trait-modal.component.ts

@@ -1,6 +1,6 @@
 import { Component, Output, EventEmitter, Input } from '@angular/core';
-import { NgxSmartModalService } from 'ngx-smart-modal';
 import { Trait } from 'src/interfaces/traits';
+import { ModalService } from 'src/services/modal/modal.service';
 
 @Component({
   selector: 'trait-modal',
@@ -8,63 +8,51 @@ import { Trait } from 'src/interfaces/traits';
   styleUrls: ['./trait-modal.component.scss'],
 })
 export class TraitModalComponent {
-  public constructor(public ngxSmartModalService: NgxSmartModalService) {}
+  @Input() public isUpdate: boolean = false;
+  @Input() public trait: Trait | undefined;
 
-  @Output() public traitCreated: EventEmitter<Trait> =
-    new EventEmitter<Trait>();
+  public name: string = '';
+  public shortDescription: string = '';
+  public longDescription: string = '';
+  public origin: string = '';
 
-  @Output() public traitUpdated: EventEmitter<any> = new EventEmitter<any>();
+  public constructor(public modalAccessor: ModalService) {}
 
-  @Output() public traitDelete: EventEmitter<number> =
-    new EventEmitter<number>();
+  ngOnInit(): void {
+    if (this.isUpdate) {
+      this.loadItem();
+    }
+  }
 
-  @Input() public isToUpdate: boolean = false;
-  @Input() public traitToUpdate: Trait | undefined;
+  // FUNCTIONS
 
-  public newTraitName: string = '';
-  public newTraitShortDescription: string = '';
-  public newTraitLongDescription: string = '';
-  public newTraitOrigin: string = '';
+  public loadItem(): void {
+    this.name = this.trait!.name;
+    this.shortDescription = this.trait!.shortDescription;
+    this.longDescription = this.trait!.longDescription;
+    this.origin = this.trait!.origin;
+  }
 
-  public createTrait(): void {
-    const newTrait: Trait = {
-      name: this.newTraitName,
-      shortDescription: this.newTraitShortDescription,
-      longDescription: this.newTraitLongDescription,
-      origin: this.newTraitOrigin,
+  public createTrait(): Trait {
+    return {
+      name: this.name,
+      shortDescription: this.shortDescription,
+      longDescription: this.longDescription,
+      origin: this.origin,
     };
-
-    this.traitCreated.emit(newTrait);
-    this.ngxSmartModalService.getModal('traitModal').close();
-    this.resetData();
   }
 
-  public checkIfTraitIsToUpdate(): void {
-    if (this.isToUpdate) {
-      this.newTraitName = this.traitToUpdate!.name;
-      this.newTraitShortDescription = this.traitToUpdate!.shortDescription;
-      this.newTraitLongDescription = this.traitToUpdate!.longDescription;
-      this.newTraitOrigin = this.traitToUpdate!.origin;
-    }
-  }
+  // RESPONSES
 
-  public updateTrait(): void {
-    const updatedTrait: Trait = {
-      name: this.newTraitName,
-      shortDescription: this.newTraitShortDescription,
-      longDescription: this.newTraitLongDescription,
-      origin: this.newTraitOrigin,
-    };
+  public cancel(): void {
+    this.modalAccessor.handleModalClosing('cancel', undefined);
+  }
 
-    this.traitUpdated.emit(updatedTrait);
-    this.ngxSmartModalService.getModal('traitModal').close();
-    this.resetData();
+  public add(): void {
+    this.modalAccessor.handleModalClosing('add', this.createTrait());
   }
 
-  public resetData(): void {
-    this.newTraitName = '';
-    this.newTraitShortDescription = '';
-    this.newTraitLongDescription = '';
-    this.newTraitOrigin = '';
+  public update(): void {
+    this.modalAccessor.handleModalClosing('update', this.createTrait());
   }
 }

+ 14 - 23
src/app/journal/journal-stats/ability-panel/trait-table/trait-table.component.html

@@ -1,31 +1,22 @@
 <div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
-  <div
-    class="example-box"
-    *ngFor="let trait of traits; let abilityIndex = index"
-    (click)="openDetailsPanel(abilityIndex)"
-    cdkDrag
-  >
+  @for(trait of traits; let index = $index; track trait){
+  <div class="example-box" (click)="openDetailsPanel(index)" cdkDrag>
     <!-- Eventuell Symbol für den Ursprung des Traits -->
     <div class="trait-name">{{ trait.name }}</div>
     <br />
 
     <p>{{ trait.shortDescription }}</p>
   </div>
+  }@empty{
+  <div
+    style="
+      text-align: center;
+      margin-top: 2rem;
+      font-size: 1.25rem;
+      font-weight: 500;
+    "
+  >
+    Noch keine Eigenschaft hinzugefügt
+  </div>
+  }
 </div>
-<!-- <ui-button
-  style="margin: 1rem"
-  [type]="'add'"
-  [size]="'xlarge'"
-  [color]="'primary'"
-  (click)="openTraitModal()"
->
-</ui-button> -->
-
-<trait-modal
-  (traitCreated)="addNewlyCreatedTrait($event)"
-  (traitUpdated)="updateTrait($event)"
-  [isToUpdate]="isToUpdate"
-  [traitToUpdate]="traitToUpdate"
-></trait-modal>
-
-<!-- (click)="openTraitModal(abilityIndex)" -->

+ 39 - 31
src/app/journal/journal-stats/ability-panel/trait-table/trait-table.component.ts

@@ -7,10 +7,10 @@ import {
 } from '@angular/cdk/drag-drop';
 import { DataService } from 'src/services/data/data.service';
 import { DetailsService } from 'src/services/details/details.service';
-import { NgxSmartModalService } from 'ngx-smart-modal';
 import { Trait } from 'src/interfaces/traits';
 import { TraitDetailsComponent } from './trait-details/trait-details.component';
-
+import { ModalService } from 'src/services/modal/modal.service';
+import { TraitModalComponent } from './trait-modal/trait-modal.component';
 @Component({
   selector: 'trait-table',
   templateUrl: './trait-table.component.html',
@@ -25,56 +25,58 @@ export class TraitTableComponent {
 
   public constructor(
     public dataAccessor: DataService,
-    public ngxSmartModalService: NgxSmartModalService,
+    public modalAccessor: ModalService,
     private detailsAccesssor: DetailsService
   ) {}
 
   public ngOnInit(): void {
-    this.traits = this.dataAccessor.getTraits();
+    this.traits = this.dataAccessor.traits;
   }
 
-  public drop(event: CdkDragDrop<string[]>): void {
-    moveItemInArray(this.traits, event.previousIndex, event.currentIndex);
-    this.updateDatabase();
-  }
+  // FUNCTIONS
 
-  public openTraitModal(traitIndex?: number): void {
-    if (traitIndex !== undefined) {
-      this.isToUpdate = true;
-      this.traitToUpdate = this.traits[traitIndex];
-      this.updateTraitIndex = traitIndex;
-    }
-    this.ngxSmartModalService.getModal('traitModal').open();
+  public openModal(isUpdate: boolean, index?: number): void {
+    this.modalAccessor.openModal(TraitModalComponent, {
+      trait:
+        index !== undefined
+          ? JSON.parse(JSON.stringify(this.traits[index]))
+          : undefined,
+      isUpdate: isUpdate,
+    });
+    const resultSubscription = this.modalAccessor.result$.subscribe(
+      (result) => {
+        if (result.state === 'update') {
+          this.updateTrait(result.data, index!);
+        } else if (result.state === 'add') {
+          this.addTrait(result.data);
+        }
+        resultSubscription.unsubscribe();
+      }
+    );
   }
 
   // add
-  public addNewlyCreatedTrait(trait: Trait): void {
+  public addTrait(trait: Trait): void {
     this.traits.push(trait);
     this.updateDatabase();
   }
 
   //  update
-  public updateTrait(ability: Trait): void {
-    this.traits[this.updateTraitIndex!] = ability;
+  public updateTrait(ability: Trait, index: number): void {
+    this.traits[index] = ability;
     this.updateDatabase();
-    this.resetUpdateData();
-  }
-
-  public updateDatabase(): void {
-    this.dataAccessor.setTraits(this.traits);
-  }
-
-  private resetUpdateData(): void {
-    this.isToUpdate = false;
-    this.traitToUpdate = undefined;
-    this.updateTraitIndex = undefined;
   }
 
+  // delete
   private deleteTrait(index: number): void {
     this.traits.splice(index, 1);
     this.updateDatabase();
   }
 
+  public updateDatabase(): void {
+    this.dataAccessor.traits = this.traits;
+  }
+
   // details Panel
 
   public openDetailsPanel(index: number): void {
@@ -83,14 +85,20 @@ export class TraitTableComponent {
     });
     const resultSubscription = this.detailsAccesssor.result$.subscribe(
       (result) => {
-        console.log(result);
         if (result.state === 'delete') {
           this.deleteTrait(index);
         } else if (result.state === 'update') {
-          this.openTraitModal(index);
+          this.openModal(true, index);
         }
         resultSubscription.unsubscribe();
       }
     );
   }
+
+  // UTILITY
+
+  public drop(event: CdkDragDrop<string[]>): void {
+    moveItemInArray(this.traits, event.previousIndex, event.currentIndex);
+    this.updateDatabase();
+  }
 }

+ 1 - 1
src/app/journal/journal-stats/attribute-skill-container/attribute-panel/attribute-field/attribute-field.component.html

@@ -5,7 +5,7 @@
   <div class="attribute-modifier">{{ attributeModifier }}</div>
   <div>
     <input
-      class="attribute-value"
+      type="number"
       [(ngModel)]="attribute.value"
       (change)="updateValue()"
       (click)="$event.stopPropagation()"

+ 14 - 6
src/app/journal/journal-stats/attribute-skill-container/attribute-panel/attribute-field/attribute-field.component.scss

@@ -1,4 +1,4 @@
-.attribute-box{
+.attribute-box {
     border: solid 1px var(--border-color);
     background-color: var(--field-background-color);
     box-shadow: var(--shadow-small);
@@ -6,12 +6,12 @@
     text-align: center;
     cursor: pointer;
 
-    .attribute-name{
+    .attribute-name {
         cursor: pointer;
         font-weight: 600;
     }
 
-    input{
+    input {
         border: none;
         outline: none;
         box-shadow: none;
@@ -22,10 +22,18 @@
         font-weight: 600;
     }
 
-    .attribute-modifier{
+    input[type="number"]::-webkit-inner-spin-button,
+    input[type="number"]::-webkit-outer-spin-button {
+        -webkit-appearance: none;
+        margin: 0;
+    }
+
+    input[type="number"] {
+        -moz-appearance: textfield;
+    }
+
+    .attribute-modifier {
         font-size: 2rem;
         font-weight: 700;
     }
 }
-
-        

+ 0 - 21
src/app/journal/journal-stats/attribute-skill-container/attribute-panel/attribute-field/attribute-field.component.ts

@@ -42,14 +42,6 @@ export class AttributeFieldComponent {
     observable.subscribe((newValue: Attribute) => {
       this.attribute = newValue;
       this.attributeModifier = this.calculateModifier();
-      this.saveModifier = this.calculateSaveModifier();
-    });
-  }
-
-  private initProficiencySubscription(): void {
-    this.dataAccessor.proficiency$.subscribe((newValue: any) => {
-      this.proficiencyBonus = newValue.value;
-      this.saveModifier = this.calculateSaveModifier();
     });
   }
 
@@ -70,20 +62,7 @@ export class AttributeFieldComponent {
     }
   }
 
-  public calculateSaveModifier(): string {
-    let mod: number = Math.floor((this.attribute.value - 10) / 2);
-    if (this.attribute.proficiency) {
-      mod += this.proficiencyBonus;
-    }
-    if (mod > 0) {
-      return '+' + mod;
-    } else {
-      return mod.toString();
-    }
-  }
-
   public openDetails(): void {
-    console.log(this.attribute);
     this.detailsAccessor.openPanel(AttributeDetailsComponent, {
       attribute: this.attribute,
       modifier: this.attributeModifier,

+ 4 - 2
src/app/journal/journal-stats/attribute-skill-container/save-throw-panel/save-throw-field/save-throw-field.component.html

@@ -3,10 +3,12 @@
     class="save-throw-field__input"
     type="checkbox"
     [(ngModel)]="attribute.proficiency"
-    (click)="$event.stopPropagation()"
+    (click)="updateAttribute(); $event.stopPropagation()"
   />
 
-  <div class="save-throw-field__name">{{ nameTranslator[attributeName] }}</div>
+  <div class="save-throw-field__name">
+    {{ nameTranslator[attributeName] }}
+  </div>
 
   <div class="save-throw-field__value">{{ saveModifier }}</div>
 </div>

+ 7 - 2
src/app/journal/journal-stats/attribute-skill-container/save-throw-panel/save-throw-field/save-throw-field.component.ts

@@ -17,7 +17,6 @@ export class SaveThrowFieldComponent {
   ) {}
 
   @Input() attributeName: string = '';
-
   public attribute: Attribute = { name: '', value: 0, proficiency: false };
 
   private proficiencyBonus: number = 0;
@@ -51,7 +50,7 @@ export class SaveThrowFieldComponent {
 
   private initProficiencySubscription(): void {
     this.dataAccessor.proficiency$.subscribe((newValue: any) => {
-      this.proficiencyBonus = newValue.value;
+      this.proficiencyBonus = newValue;
       this.attributeModifier = this.calculateAttributeModifier();
       this.saveModifier = this.calculateSaveModifier();
     });
@@ -79,4 +78,10 @@ export class SaveThrowFieldComponent {
       saveModifier: this.saveModifier,
     });
   }
+
+  public updateAttribute(): void {
+    setTimeout(() => {
+      this.dataAccessor.updateAttribute(this.attribute);
+    });
+  }
 }

+ 9 - 9
src/app/journal/journal-stats/attribute-skill-container/skill-panel/skill-field/skill-field.component.ts

@@ -29,24 +29,24 @@ export class SkillFieldComponent {
     animalHandling: { skill: 'Tierkunde', attribute: 'WIS', long: 'wisdom' },
     arcana: { skill: 'Arkana', attribute: 'INT', long: 'intelligence' },
     athletics: { skill: 'Athletik', attribute: 'STR', long: 'strength' },
-    deception: { skill: 'Täuschung', attribute: 'CHA', long: 'charisma' },
+    deception: { skill: 'Täuschen', attribute: 'CHA', long: 'charisma' },
     history: { skill: 'Geschichte', attribute: 'INT', long: 'intelligence' },
-    insight: { skill: 'Einsicht', attribute: 'WIS', long: 'wisdom' },
+    insight: { skill: 'Motiv erkennen', attribute: 'WIS', long: 'wisdom' },
     intimidation: {
       skill: 'Einschüchtern',
       attribute: 'CHA',
       long: 'charisma',
     },
     investigation: {
-      skill: 'Ermittlung',
+      skill: 'Nachforschung',
       attribute: 'INT',
       long: 'intelligence',
     },
-    medicine: { skill: 'Medizin', attribute: 'WIS', long: 'wisdom' },
-    nature: { skill: 'Natur', attribute: 'INT', long: 'intelligence' },
+    medicine: { skill: 'Heilkunde', attribute: 'WIS', long: 'wisdom' },
+    nature: { skill: 'Naturkunde', attribute: 'INT', long: 'intelligence' },
     perception: { skill: 'Wahrnehmung', attribute: 'WIS', long: 'wisdom' },
-    performance: { skill: 'Performance', attribute: 'CHA', long: 'charisma' },
-    persuasion: { skill: 'Überredung', attribute: 'CHA', long: 'charisma' },
+    performance: { skill: 'Auftreten', attribute: 'CHA', long: 'charisma' },
+    persuasion: { skill: 'Überzeugen', attribute: 'CHA', long: 'charisma' },
     religion: { skill: 'Religion', attribute: 'INT', long: 'intelligence' },
     sleightOfHand: {
       skill: 'Fingerfertigkeit',
@@ -54,7 +54,7 @@ export class SkillFieldComponent {
       long: 'dexterity',
     },
     stealth: { skill: 'Heimlichkeit', attribute: 'GES', long: 'dexterity' },
-    survival: { skill: 'Überleben', attribute: 'WIS', long: 'wisdom' },
+    survival: { skill: 'Überlebenskunst', attribute: 'WIS', long: 'wisdom' },
   };
 
   ngOnInit(): void {
@@ -86,7 +86,7 @@ export class SkillFieldComponent {
 
   private initProficiencySubscription(): void {
     this.dataAccessor.proficiency$.subscribe((newValue: any) => {
-      this.proficiencyBonus = newValue.value;
+      this.proficiencyBonus = newValue;
       this.skillModifier = this.calculateModifier();
     });
   }

+ 9 - 9
src/app/journal/journal-stats/attribute-skill-container/skill-panel/skill-panel.component.ts

@@ -8,22 +8,22 @@ import { Component } from '@angular/core';
 export class SkillPanelComponent {
   public skills: any = [
     'acrobatics',
-    'animalHandling',
     'arcana',
     'athletics',
-    'deception',
+    'performance',
+    'intimidation',
+    'sleightOfHand',
     'history',
+    'medicine',
+    'stealth',
+    'animalHandling',
     'insight',
-    'intimidation',
     'investigation',
-    'medicine',
     'nature',
-    'perception',
-    'performance',
-    'persuasion',
     'religion',
-    'sleightOfHand',
-    'stealth',
+    'deception',
     'survival',
+    'persuasion',
+    'perception',
   ];
 }

+ 1 - 0
src/app/journal/journal-stats/info-row/conditions/conditions.component.html

@@ -21,6 +21,7 @@
       style="width: 6rem"
       [(ngModel)]="exhaustion"
       (change)="updateExhaustion()"
+      (click)="$event.stopPropagation()"
     />
     <div class="info-label">Erschöpfung</div>
   </div>

+ 4 - 2
src/app/journal/journal-stats/info-row/conditions/conditions.component.ts

@@ -16,7 +16,7 @@ export class ConditionsComponent {
   ) {}
 
   public exhaustion: number = 0;
-  public conditions: string[] = ['Blind', 'Kampfunfähig'];
+  public conditions: string[] = [];
 
   public availableConditions: string[] = [
     'Betäubt',
@@ -54,9 +54,11 @@ export class ConditionsComponent {
     });
     const resultSubscription = this.detailsAccessor.result$.subscribe(
       (result) => {
-        console.log(result);
         if (result.state === 'update') {
           this.conditions = result.data;
+          this.dataAccessor.conditions = this.conditions;
+        } else {
+          throw new Error('DND-ERROR: Invalid state');
         }
         resultSubscription.unsubscribe();
       }

+ 2 - 5
src/app/journal/journal-stats/info-row/proficiency/proficiency-field.component.ts

@@ -22,15 +22,12 @@ export class ProficiencyFieldComponent {
 
   private initAttributeSubscription(): void {
     this.dataAccessor.proficiency$.subscribe((newValue: any) => {
-      this.proficiency = newValue.value;
+      this.proficiency = newValue;
     });
   }
 
   public updateValue(): void {
-    this.dataAccessor.updateCharacterData({
-      name: 'proficiency',
-      value: this.proficiency,
-    });
+    this.dataAccessor.updateProficiencyBonus(this.proficiency);
   }
 
   public openDetails(): void {

+ 1 - 0
src/app/journal/journal-stats/life-container/life/life-details/life-details.component.html

@@ -20,6 +20,7 @@
     <div for="" class="life-label">Temporäre Trefferpunkte</div>
   </div>
 </div>
+<app-hit-dice></app-hit-dice>
 
 <div class="vertical-button-wrapper-2">
   <ui-button

+ 2 - 5
src/app/journal/journal-stats/life-container/life/life-details/life-details.component.ts

@@ -29,11 +29,6 @@ export class LifeDetailsComponent {
 
   public close(result: string): void {
     if (result === 'update') {
-      console.log(
-        this.maxHitPoints,
-        this.currentHitPoints,
-        this.temporaryHitPoints
-      );
       this.detailsAccessor.closePanel(result, {
         maxHitPoints: this.maxHitPoints,
         currentHitPoints: this.currentHitPoints,
@@ -41,6 +36,8 @@ export class LifeDetailsComponent {
       });
     } else if (result === 'dismiss') {
       this.detailsAccessor.closePanel(result);
+    } else {
+      throw new Error('Unknown result: ' + result);
     }
   }
 }

+ 6 - 5
src/app/journal/journal-stats/life-container/life/life.component.ts

@@ -23,10 +23,10 @@ export class LifeComponent {
   public missingHitPointsPercentage: number = 0;
 
   ngOnInit(): void {
-    const lifeData = this.dataAccessor.hitPoints;
-    this.maxHitPoints = lifeData.maxHitPoints;
-    this.currentHitPoints = lifeData.currentHitPoints;
-    this.temporaryHitPoints = lifeData.temporaryHitPoints;
+    const hitPointsData = this.dataAccessor.hitPoints;
+    this.maxHitPoints = hitPointsData.maxHitPoints;
+    this.currentHitPoints = hitPointsData.currentHitPoints;
+    this.temporaryHitPoints = hitPointsData.temporaryHitPoints;
     this.calculatePercentages();
   }
 
@@ -80,13 +80,14 @@ export class LifeComponent {
     // The result from the details panel with return values
     const resultSubscription = this.detailsAccessor.result$.subscribe(
       (result) => {
-        console.log(result);
         if (result.state === 'update') {
           this.maxHitPoints = result.data.maxHitPoints;
           this.currentHitPoints = result.data.currentHitPoints;
           this.temporaryHitPoints = result.data.temporaryHitPoints;
           this.calculatePercentages();
           this.updateDatabase();
+        } else {
+          throw new Error('DND-Error: Unknown state: ' + result.state);
         }
         resultSubscription.unsubscribe();
       }

+ 32 - 8
src/app/journal/journal-stats/weapons-container/spell-table/spell-table.component.html

@@ -7,7 +7,7 @@
       <div>Bonus/</div>
       <div>Save</div>
     </div>
-    <div>Schaden</div>
+    <div>Schaden/ Heilung</div>
     <div>Reichweite</div>
   </div>
   <div
@@ -16,12 +16,8 @@
     class="example-list table-content"
     (cdkDropListDropped)="dropSpells($event)"
   >
-    <div
-      class="example-box"
-      *ngFor="let spell of spells; let index = index"
-      cdkDrag
-      (click)="openDetailsPanel(index)"
-    >
+    @for(spell of spells; let index = $index; track spell){
+    <div class="example-box" cdkDrag (click)="showFullSpellcard(index)">
       <!--  Range Icon -->
       <ng-container
         [ngTemplateOutlet]="distanceIconTemplate"
@@ -63,7 +59,35 @@
         [ngTemplateOutletContext]="{ spell: spell }"
       ></ng-container>
     </div>
+    }@empty{
+    <div
+      style="
+        text-align: center;
+        margin-top: 2rem;
+        font-size: 1.25rem;
+        font-weight: 500;
+      "
+    >
+      Noch keine Zauber hinzugefügt
+    </div>
+    }
   </div>
+  <input
+    id="typeahead-basic"
+    type="text"
+    [class]="showInput ? 'spellInput' : 'spellInput hidden'"
+    (selectItem)="onSpellSelect($event.item); $event.preventDefault()"
+    [(ngModel)]="newSpellName"
+    [ngbTypeahead]="search"
+    placement="top-start"
+    placeholder="Name des vorbereiteten Zaubers"
+  />
+  <button
+    [class]="showInput ? 'slide-button cancel-button' : 'slide-button'"
+    (click)="toggleInput()"
+  >
+    @if(showInput){ Abbrechen} @else { Hinzufügen}
+  </button>
 </div>
 
 <!-- Templates -->
@@ -145,7 +169,7 @@
 <ng-template #spellRangeTemplate let-spell="spell">
   <div class="spell-range">
     <div *ngIf="spell.isRanged">{{ spell.range }} ft.</div>
-    <div *ngIf="!spell.isRanged">5 ft.</div>
+    <div *ngIf="!spell.isRanged">Berührung</div>
 
     <div *ngIf="spell.hasAreaOfEffect">
       <span>{{ spell.radius }} ft. {{ areas[spell.areaOfEffectType] }} </span>

+ 64 - 25
src/app/journal/journal-stats/weapons-container/spell-table/spell-table.component.scss

@@ -1,4 +1,4 @@
-.example-list { 
+.example-list {
   max-width: 100%;
   min-height: 60px;
   display: block;
@@ -11,54 +11,57 @@
   margin: 15px 10px;
   color: rgba(0, 0, 0, 0.87);
   display: grid;
-  grid-template-columns: 6fr .1fr 20fr .1fr 10fr .1fr 8fr .1fr 20fr .1fr 16fr;
+  grid-template-columns: 6fr 0.1fr 20fr 0.1fr 10fr 0.1fr 8fr 0.1fr 20fr 0.1fr 16fr;
   align-items: center;
   justify-content: space-between;
   box-sizing: border-box;
   cursor: move;
   background: var(--primary-color-light);
-  border-radius: 10px;;
+  border-radius: 10px;
   font-size: 1rem;
-  font-weight:600;
+  font-weight: 600;
   text-align: center;
-  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
-              0 8px 8px 1px rgba(0, 0, 0, 0.14),
-              0 3px 10px 2px rgba(0, 0, 0, 0.12);
+  box-shadow:
+    0 5px 5px -3px rgba(0, 0, 0, 0.2),
+    0 8px 8px 1px rgba(0, 0, 0, 0.14),
+    0 3px 10px 2px rgba(0, 0, 0, 0.12);
 }
 
-.spell-box{
+.spell-box {
   height: 100%;
-  display:flex;
+  width: 100%;
+  display: flex;
   flex-direction: column;
+  position: relative;
 }
 
-.heading-list{
+.heading-list {
   flex: 0 0 3rem;
   margin: 0.5rem 0.625rem;
   display: grid;
   grid-template-columns: 6fr 20fr 10fr 8fr 20fr 16fr;
-  text-align:center;
+  text-align: center;
   font-weight: 700;
 }
 
-.table-content{
-  flex: 0 0 calc(100% - 4rem);
+.table-content {
+  flex: 0 0 calc(100% - 9rem);
   overflow-y: auto;
-    }
+}
 
-.bold{
+.bold {
   font-weight: bold;
 }
 
-.small{
-  font-size: .625rem;
+.small {
+  font-size: 0.625rem;
 }
 
-.large{
+.large {
   font-size: 1.125rem;
 }
 
-.vertical-line{
+.vertical-line {
   position: relative;
   width: 1px;
   height: 3.5rem;
@@ -67,20 +70,56 @@
 .vertical-line::before {
   content: "";
   position: absolute;
-  top: 15%; 
-  bottom: 15%; 
+  top: 15%;
+  bottom: 15%;
   left: 0;
   border-left: 1px solid black;
 }
 
+.slide-button {
+  position: absolute;
+  height: 3rem;
+  bottom: 1rem;
+  right: 10%;
+  width: 80%;
+  border-radius: 10px;
+  background: var(--accept);
+  transition: width 0.25s ease-in-out;
+  box-shadow: var(--shadow);
+
+  &:hover {
+    background-color: var(--accept-hover);
+  }
+}
+
+.cancel-button {
+  width: 20%;
+  background-color: var(--delete);
+
+  &:hover {
+    background-color: var(--delete-hover);
+  }
+}
+
+.spellInput {
+  position: absolute;
+  height: 3rem;
+  bottom: 1rem;
+  right: 10%;
+  width: 80%;
+  border-radius: 10px;
+  box-shadow: var(--shadow);
+}
+
 //// Drag and Drop
 
 .cdk-drag-preview {
   box-sizing: border-box;
   border-radius: 10px;
-  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
-              0 8px 10px 1px rgba(0, 0, 0, 0.14),
-              0 3px 14px 2px rgba(0, 0, 0, 0.12);
+  box-shadow:
+    0 5px 5px -3px rgba(0, 0, 0, 0.2),
+    0 8px 10px 1px rgba(0, 0, 0, 0.14),
+    0 3px 14px 2px rgba(0, 0, 0, 0.12);
 }
 
 .cdk-drag-placeholder {
@@ -97,4 +136,4 @@
 
 .example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
   transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
-}
+}

+ 78 - 9
src/app/journal/journal-stats/weapons-container/spell-table/spell-table.component.ts

@@ -6,6 +6,9 @@ import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
 import { Spell } from 'src/interfaces/spell';
 import { SpellDetailsComponent } from './spell-details/spell-details.component';
 import { SpellModalComponent } from 'src/app/journal/spell-modal/spell-modal.component';
+import { FullSpellcardComponent } from 'src/app/shared-components/full-spellcard/full-spellcard.component';
+import { Observable, OperatorFunction } from 'rxjs';
+import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';
 
 @Component({
   selector: 'spell-table',
@@ -13,12 +16,6 @@ import { SpellModalComponent } from 'src/app/journal/spell-modal/spell-modal.com
   styleUrls: ['./spell-table.component.scss'],
 })
 export class SpellTableComponent {
-  public constructor(
-    public dataAccessor: DataService,
-    private modalAccessor: ModalService,
-    public detailsAccessor: DetailsService
-  ) {}
-
   public spellAttackBonus: string = '0';
   public spellSaveDC: number = 0;
   private spellcastingAttribute: string | undefined;
@@ -26,6 +23,10 @@ export class SpellTableComponent {
   private proficiencyBonus: number = 2;
 
   public spells!: Spell[];
+  private preparedSpells!: Spell[];
+  private preparedSpellsNames: string[] = [];
+  public newSpellName: string = '';
+  public showInput: boolean = false;
 
   public attributes: any = {
     strength: 'STR',
@@ -45,11 +46,38 @@ export class SpellTableComponent {
     cube: 'Würfel',
   };
 
+  public constructor(
+    public dataAccessor: DataService,
+    private modalAccessor: ModalService,
+    public detailsAccessor: DetailsService
+  ) {}
+
   public ngOnInit(): void {
     this.spells = this.dataAccessor.favoriteSpells;
+    this.preparedSpells = this.dataAccessor.getAllPreparedSpells();
+    this.preparedSpellsNames = this.preparedSpells.map((spell) => spell.name);
     this.subscribeToData();
   }
 
+  public showFullSpellcard(spellIndex: number): void {
+    this.modalAccessor.openModal(FullSpellcardComponent, {
+      spell: this.spells[spellIndex],
+      isFromDashboard: true,
+    });
+    const resultSubscription = this.modalAccessor.result$.subscribe(
+      (result) => {
+        resultSubscription.unsubscribe();
+        if (result.state === 'delete') {
+          this.spells.splice(spellIndex, 1);
+          this.updateSpellsInDatabase();
+        } else if (result.state !== 'cancel') {
+          throw new Error('Unexpected result state, please send a bug report.');
+        }
+      }
+    );
+  }
+
+  // LEGACY CODE
   public openDetailsPanel(index: number): void {
     this.detailsAccessor.openPanel(SpellDetailsComponent, {
       spell: this.spells[index],
@@ -60,16 +88,18 @@ export class SpellTableComponent {
     });
     const resultSubscription = this.detailsAccessor.result$.subscribe(
       (result) => {
-        console.log(result);
         if (result.state === 'delete') {
           this.deleteSpell(index);
         } else if (result.state === 'update') {
           this.openModal(true, index);
+        } else {
+          throw new Error('DND-Error: Unknown state: ' + result.state);
         }
         resultSubscription.unsubscribe();
       }
     );
   }
+  //
 
   public openModal(isUpdate: boolean, index?: number): void {
     this.modalAccessor.openModal(SpellModalComponent, {
@@ -82,16 +112,34 @@ export class SpellTableComponent {
     const resultSubscription = this.modalAccessor.result$.subscribe(
       (result) => {
         if (result.state === 'update') {
-          console.log(result.data);
           this.updateSpell(result.data, index!);
         } else if (result.state === 'add') {
           this.addSpell(result.data);
+        } else {
+          throw new Error('DND-Error: Unknown state: ' + result.state);
         }
         resultSubscription.unsubscribe();
       }
     );
   }
 
+  public toggleInput(): void {
+    this.showInput = !this.showInput;
+    this.newSpellName = '';
+  }
+
+  public onSpellSelect(spellname: any): void {
+    const newSpell = this.preparedSpells.filter(
+      (spell) => spell.name === spellname
+    );
+    if (newSpell.length !== 1) {
+      throw new Error('Spell not found.');
+    } else {
+      this.addSpell(newSpell[0]);
+    }
+    this.newSpellName = '';
+  }
+
   public addSpell(spell: Spell) {
     this.spells.push(spell);
     this.updateSpellsInDatabase();
@@ -134,7 +182,7 @@ export class SpellTableComponent {
     this.spellcastingAttribute = 'intelligence';
 
     this.dataAccessor.proficiency$.subscribe((value) => {
-      this.proficiencyBonus = value.value;
+      this.proficiencyBonus = value;
       if (this.spellcastingAttribute) {
         this.computeSpellAttackBonusAndSaveDC();
       }
@@ -145,4 +193,25 @@ export class SpellTableComponent {
       this.computeSpellAttackBonusAndSaveDC();
     });
   }
+
+  public search: OperatorFunction<string, readonly string[]> = (
+    text$: Observable<string>
+  ) =>
+    text$.pipe(
+      debounceTime(200),
+      distinctUntilChanged(),
+      map((term) =>
+        term.length < 2
+          ? []
+          : this.preparedSpellsNames
+              .filter((v) => v.toLowerCase().indexOf(term.toLowerCase()) > -1)
+              .filter(
+                (v) =>
+                  !this.spells.some(
+                    (spell) => spell.name.toLowerCase() === v.toLowerCase()
+                  )
+              )
+              .slice(0, 5)
+      )
+    );
 }

+ 26 - 6
src/app/journal/journal-stats/weapons-container/weapon-table/weapon-modal/weapon-modal.component.html

@@ -69,22 +69,42 @@
     </div>
 
     <div class="input-element" *ngIf="isRanged">
-      <label for="weaponRange">Reichweite</label>
+      <label for="weaponRange">Normale Reichweite</label>
       <input
-        type="text"
+        type="number"
         class="add-input"
         id="weaponRange"
-        [(ngModel)]="range"
+        [(ngModel)]="range[0]"
+      />
+    </div>
+
+    <div class="input-element" *ngIf="isRanged">
+      <label for="weaponRange">Große Reichweite</label>
+      <input
+        type="number"
+        class="add-input"
+        id="weaponRange"
+        [(ngModel)]="range[1]"
+      />
+    </div>
+
+    <div class="input-element" *ngIf="canBeThrown">
+      <label for="weaponRange">Normale Wurfreichweite</label>
+      <input
+        type="number"
+        class="add-input"
+        id="weaponRange"
+        [(ngModel)]="throwRange[0]"
       />
     </div>
 
     <div class="input-element" *ngIf="canBeThrown">
-      <label for="weaponRange">Wurfreichweite</label>
+      <label for="weaponRange">Große Wurfreichweite</label>
       <input
-        type="text"
+        type="number"
         class="add-input"
         id="weaponRange"
-        [(ngModel)]="throwRange"
+        [(ngModel)]="throwRange[1]"
       />
     </div>
 

+ 13 - 16
src/app/journal/journal-stats/weapons-container/weapon-table/weapon-modal/weapon-modal.component.scss

@@ -1,23 +1,22 @@
-.modal-dimensions{
+.modal-dimensions {
     width: 40vw;
-    background-color: antiquewhite;
+    background-color: var(--modal-background);
     border-radius: 10px;
     border: 1px solid var(--border-color);
     padding: 1rem;
 }
 
-.add-form-group{
+.add-form-group {
     display: flex;
     flex-direction: column;
     gap: 1rem;
-    
 }
-.input-element{
+.input-element {
     display: flex;
     flex-direction: column;
 }
 
-.checkbox-element{
+.checkbox-element {
     display: flex;
     flex-direction: column;
     align-items: center;
@@ -25,23 +24,22 @@
     flex-basis: 33.33%;
 }
 
-.form-element-row{
+.form-element-row {
     display: flex;
     flex-direction: row;
     flex-wrap: wrap;
     justify-content: space-around;
-    text-align:center;
+    text-align: center;
     row-gap: 1rem;
 }
 
-.damage-container{
+.damage-container {
     display: flex;
     flex-direction: row;
     gap: 1rem;
-    
 }
 
-.damage-row{
+.damage-row {
     display: flex;
     flex-direction: row;
     justify-content: center;
@@ -49,22 +47,22 @@
     gap: 0.1rem;
 }
 
-.damage-box{
-    display:flex;
+.damage-box {
+    display: flex;
     flex-direction: column;
     align-items: left;
     gap: 0.5rem;
     flex-basis: 30%;
 }
 
-.dice-row{
+.dice-row {
     display: flex;
     flex-direction: row;
     gap: 1rem;
     margin-bottom: 1rem;
 }
 
-.button-wrapper{
+.button-wrapper {
     width: 100%;
     display: grid;
     grid-template-rows: 1fr 1fr;
@@ -75,4 +73,3 @@
     align-items: center;
     justify-content: center;
 }
-

+ 4 - 9
src/app/journal/journal-stats/weapons-container/weapon-table/weapon-modal/weapon-modal.component.ts

@@ -14,8 +14,8 @@ export class WeaponModalComponent {
   @Input() public isUpdate: boolean = false;
 
   public name: string = '';
-  public range: string = '5 ft.';
-  public throwRange: string = '';
+  public range: number[] = [5, 5];
+  public throwRange: number[] = [5, 5];
   public attackBonus: string = '+0';
   public damage: Damage[] = [{ diceNumber: '', diceType: '', damageType: '' }];
   public proficient: boolean = false;
@@ -141,12 +141,7 @@ export class WeaponModalComponent {
     this.resetItem();
   }
 
-  /**
-   * Creates a new Weapon object with the updated or newly generated values.
-   * @returns The created item
-   */
   public createItem(): Weapon {
-    console.log('createItem() in weapon-modal.component.ts');
     return {
       name: this.name,
       range: this.range,
@@ -171,8 +166,8 @@ export class WeaponModalComponent {
    */
   public resetItem(): void {
     this.name = '';
-    this.range = '5 ft.';
-    this.throwRange = '';
+    this.range = [5, 5];
+    this.throwRange = [5, 5];
     this.attackBonus = '+0';
     this.damage = [{ diceNumber: '', diceType: '', damageType: '' }];
     this.proficient = false;

+ 19 - 14
src/app/journal/journal-stats/weapons-container/weapon-table/weapon-table.component.html

@@ -12,12 +12,8 @@
     class="example-list table-content"
     (cdkDropListDropped)="dropWeapons($event)"
   >
-    <div
-      class="example-box"
-      *ngFor="let weapon of weapons; let index = index"
-      cdkDrag
-      (click)="openDetailsPanel(index)"
-    >
+    @for(weapon of weapons; let index = $index; track weapon){
+    <div class="example-box" cdkDrag (click)="openDetailsPanel(index)">
       <!--  Range Icon -->
       <ng-container
         [ngTemplateOutlet]="distanceIconTemplate"
@@ -44,6 +40,18 @@
         [ngTemplateOutletContext]="{ weapon: weapon }"
       ></ng-container>
     </div>
+    } @empty{
+    <div
+      style="
+        text-align: center;
+        margin-top: 3rem;
+        font-size: 1.25rem;
+        font-weight: 500;
+      "
+    >
+      Noch keine Waffen hinzugefügt
+    </div>
+    }
   </div>
 </div>
 
@@ -91,12 +99,9 @@
 
 <!-- Range -->
 <ng-template #weaponRangeTemplate let-weapon="weapon">
-  <div class="weapon-range">{{ weapon.range }}</div>
+  @if(weapon.isRanged){
+  <div class="weapon-range">
+    {{ weapon.range[0] }}/{{ weapon.range[1] }} ft.
+  </div>
+  }@else{ Nahkampf }
 </ng-template>
-
-<!-- <weapon-modal
-  #weaponTable
-  (weaponCreated)="addWeapon($event)"
-  (weaponUpdated)="updateWeapon($event.weapon, $event.index)"
-  (weaponDelete)="deleteWeapon($event)"
-></weapon-modal> -->

+ 4 - 3
src/app/journal/journal-stats/weapons-container/weapon-table/weapon-table.component.ts

@@ -56,7 +56,7 @@ export class WeaponTableComponent {
         this.calculateDamageModifierArray();
       }
     });
-    this.weapons = this.dataAccessor.getWeapons();
+    this.weapons = this.dataAccessor.favoriteWeapons;
     this.calculateDamageModifierArray();
   }
 
@@ -98,7 +98,7 @@ export class WeaponTableComponent {
   }
 
   public updateWeaponInDatabase(): void {
-    this.dataAccessor.setWeapons(this.weapons);
+    this.dataAccessor.favoriteWeapons = this.weapons;
   }
 
   public addWeapon(weapon: Weapon) {
@@ -114,11 +114,12 @@ export class WeaponTableComponent {
     });
     const resultSubscription = this.detailsAccessor.result$.subscribe(
       (result) => {
-        console.log(result);
         if (result.state === 'delete') {
           this.deleteWeapon(index);
         } else if (result.state === 'update') {
           this.openModal(true, index);
+        } else {
+          throw new Error('DND-Error: Unknown state: ' + result.state);
         }
         resultSubscription.unsubscribe();
       }

+ 3 - 22
src/app/journal/journal-stats/weapons-container/weapons-container.component.html

@@ -1,22 +1,3 @@
-<!-- <div class="weapon-spell-container">
-  <ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs">
-    <li [ngbNavItem]="1">
-      <button ngbNavLink>Waffen</button>
-      <ng-template ngbNavContent>
-        <weapon-table></weapon-table>
-      </ng-template>
-    </li>
-    <li [ngbNavItem]="2">
-      <button ngbNavLink>Zauber</button>
-      <ng-template ngbNavContent>
-        <spell-table></spell-table>
-      </ng-template>
-    </li>
-  </ul>
-
-  <div [ngbNavOutlet]="nav" class="mt-2"></div>
-</div> -->
-
 <div class="weapon-spell-container">
   <div class="tab-row">
     <button
@@ -36,9 +17,6 @@
   </div>
   @switch(active){ @case(1){
   <weapon-table #weaponTable></weapon-table>
-  } @case(2){
-  <spell-table #spellTable></spell-table>
-  } }
   <ui-button
     [type]="'add'"
     [size]="'xlarge'"
@@ -46,4 +24,7 @@
     class="button-margin"
     (click)="openModal()"
   ></ui-button>
+  } @case(2){
+  <spell-table #spellTable></spell-table>
+  } }
 </div>

+ 63 - 90
src/app/journal/journal-stats/weapons-container/weapons-container.component.scss

@@ -1,56 +1,55 @@
-.weapon-spell-container{
+.weapon-spell-container {
   border: solid 1px var(--border-color);
-    // background-color: var(--field-background-color);
-    box-shadow: var(--shadow-small);
-    border-radius: 10px;
-    height: 35.5rem;
-    display:flex;
-    flex-direction: column;
-}
-
-.tab-row{
-    display: flex;
-    flex: 0 0 3rem;
-    > *{
-        flex: 1 1 0;
-    }
-    > :first-child{
-      border-radius: 10px 0 0 0
-    }
-    > :last-child{
-      border-radius: 0 10px 0 0
-    }
-}
-
-.tab-button{
-    height: 2.25rem;
-    font-size: 1.375rem;
-    font-weight: 600;
-    color: black;
-    border: solid 1px var(--border-color);
-    transition: all 0.25s ease-in-out;
-    background-color: var(--primary-color-light);
-
-    &.active{
-        height: 2.75rem;
-        background-color: var(--primary-color);
-    }
-}
-
-
-weapon-table{
-    // 100% - tabbar height - add button height
-    height: calc(100% - 8rem);
-}
-
-spell-table{
+  // background-color: var(--field-background-color);
+  box-shadow: var(--shadow-small);
+  border-radius: 10px;
+  height: 35.5rem;
+  display: flex;
+  flex-direction: column;
+}
+
+.tab-row {
+  display: flex;
+  flex: 0 0 3rem;
+  > * {
+    flex: 1 1 0;
+  }
+  > :first-child {
+    border-radius: 10px 0 0 0;
+  }
+  > :last-child {
+    border-radius: 0 10px 0 0;
+  }
+}
+
+.tab-button {
+  height: 2.25rem;
+  font-size: 1.375rem;
+  font-weight: 600;
+  color: black;
+  border: solid 1px var(--border-color);
+  transition: all 0.25s ease-in-out;
+  background-color: var(--primary-color-light);
+
+  &.active {
+    height: 2.75rem;
+    background-color: var(--primary-color);
+  }
+}
+
+weapon-table {
   // 100% - tabbar height - add button height
-    height: calc(100% - 8rem);
+  height: calc(100% - 8rem);
 }
 
-.button-margin{
-    margin: 1rem 0rem;
+spell-table {
+  // 100% - tabbar height - add button height
+  height: calc(100% - 3rem);
+  // height: 100%;
+}
 
+.button-margin {
+  margin: 1rem 0rem;
 }
 
 .example-list {
@@ -63,12 +62,12 @@ spell-table{
   overflow: hidden;
 }
 
-.damage-list{
+.damage-list {
   display: flex;
   flex-direction: column;
 }
 
-.damage-row{
+.damage-row {
   display: flex;
   flex-direction: row;
   justify-content: center;
@@ -76,7 +75,6 @@ spell-table{
   gap: 0.1rem;
 }
 
-
 .example-box {
   padding: 20px 10px;
   border-bottom: solid 1px #ccc;
@@ -89,7 +87,7 @@ spell-table{
   cursor: move;
   background: white;
   font-size: 14px;
-  input{
+  input {
     border: none;
     background: transparent;
     text-align: center;
@@ -98,82 +96,58 @@ spell-table{
 
 //////////////// list item elemens ////////////////
 
-
-
-
-
-
-
-
-
-
-
-
-
 //////////////////////////////////////////////////
 
-
-
-
-.vertical-line{
+.vertical-line {
   width: 1px;
   height: 3rem;
-  border-left: solid 1px rgb(121, 121, 121)
+  border-left: solid 1px rgb(121, 121, 121);
 }
 
-.weapon-type{
+.weapon-type {
   width: 2rem;
   text-align: center;
 }
 
-.weapon-proficient{
+.weapon-proficient {
   width: 2rem;
   text-align: center;
 }
 
-.weapon-name{
+.weapon-name {
   width: 6rem;
   text-align: center;
 }
 
-.weapon-attack-bonus{
+.weapon-attack-bonus {
   width: 2rem;
   text-align: center;
 }
 
-.weapon-damage{
+.weapon-damage {
   width: 3rem;
   text-align: center;
 }
 
-.weapon-range{
+.weapon-range {
   width: 4rem;
   text-align: center;
 }
 
-.weapon-edit{
+.weapon-edit {
   width: 3rem;
   text-align: center;
 }
 
-
-
-
-
-
-
-
-
-
-
 // Drag and Drop
 
 .cdk-drag-preview {
   box-sizing: border-box;
   border-radius: 4px;
-  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
-              0 8px 10px 1px rgba(0, 0, 0, 0.14),
-              0 3px 14px 2px rgba(0, 0, 0, 0.12);
+  box-shadow:
+    0 5px 5px -3px rgba(0, 0, 0, 0.2),
+    0 8px 10px 1px rgba(0, 0, 0, 0.14),
+    0 3px 14px 2px rgba(0, 0, 0, 0.12);
 }
 
 .cdk-drag-placeholder {
@@ -191,4 +165,3 @@ spell-table{
 .example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
   transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
 }
-

+ 0 - 2
src/app/journal/journal-stats/weapons-container/weapons-container.component.ts

@@ -1,12 +1,10 @@
 import { Component, ViewChild } from '@angular/core';
-import { NgxSmartModalService } from 'ngx-smart-modal';
 @Component({
   selector: 'app-weapons-container',
   templateUrl: './weapons-container.component.html',
   styleUrls: ['./weapons-container.component.scss'],
 })
 export class WeaponsContainerComponent {
-  public constructor(public ngxSmartModalService: NgxSmartModalService) {}
   @ViewChild('weaponTable') weaponTable: any;
   @ViewChild('spellTable') spellTable: any;
 

+ 16 - 2
src/app/journal/journal.module.ts

@@ -4,7 +4,13 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
 import { FormsModule } from '@angular/forms';
 import { CdkTableModule } from '@angular/cdk/table';
 import { DragDropModule } from '@angular/cdk/drag-drop';
-import { NgxSmartModalModule } from 'ngx-smart-modal';
+import { ReactiveFormsModule } from '@angular/forms';
+
+// Material Design
+import { MatSlideToggleModule } from '@angular/material/slide-toggle';
+import { MatSelectModule } from '@angular/material/select';
+import { MatInputModule } from '@angular/material/input';
+import { MatFormFieldModule } from '@angular/material/form-field';
 
 import { JournalRoutingModule } from './journal-routing.module';
 import { JournalHomeComponent } from './journal-home/journal-home.component';
@@ -76,6 +82,8 @@ import { JournalSettingsComponent } from './journal-settings/journal-settings.co
 import { SimpleItemModalComponent } from './journal-inventory/simple-item-modal/simple-item-modal.component';
 import { FoodDetailsComponent } from './journal-inventory/food-details/food-details.component';
 import { TabBarComponent } from './journal-home/tab-bar/tab-bar.component';
+import { SpellcardComponent } from './journal-spellcards/spellcard/spellcard.component';
+import { AddCardComponent } from './journal-spellcards/add-card/add-card.component';
 
 @NgModule({
   declarations: [
@@ -146,6 +154,8 @@ import { TabBarComponent } from './journal-home/tab-bar/tab-bar.component';
     FoodDetailsComponent,
     TabBarComponent,
     WeaponModalComponent,
+    SpellcardComponent,
+    AddCardComponent,
   ],
   imports: [
     CommonModule,
@@ -154,8 +164,12 @@ import { TabBarComponent } from './journal-home/tab-bar/tab-bar.component';
     FormsModule,
     CdkTableModule,
     DragDropModule,
-    NgxSmartModalModule.forChild(),
     SharedComponentsModule,
+    ReactiveFormsModule,
+    MatSlideToggleModule,
+    MatSelectModule,
+    MatInputModule,
+    MatFormFieldModule,
   ],
 })
 export class JournalModule {}

+ 1 - 2
src/app/journal/spell-modal/spell-modal.component.html

@@ -48,7 +48,7 @@
             *ngFor="let level of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
             [value]="level"
           >
-            {{ level }}
+            @if(level == 0){Zaubertrick}@else{ {{ level }}}
           </option>
         </select>
         <label>Stufe</label>
@@ -165,7 +165,6 @@
     </div>
   </div>
 </div>
-<!-- </ngx-smart-modal> -->
 
 <!-- templates -->
 

+ 2 - 3
src/app/journal/spell-modal/spell-modal.component.ts

@@ -134,7 +134,6 @@ export class SpellModalComponent {
   }
 
   private loadspell(): void {
-    console.log(this.spell);
     this.name = this.spell.name;
     this.level = this.spell.level;
     this.cost = this.spell.cost;
@@ -163,9 +162,10 @@ export class SpellModalComponent {
   private createSpell(): Spell {
     const spell: Spell = {
       name: this.name,
+      duration: 0, // FIXME: only mocked
       damage: this.damage,
       heal: this.heal,
-      level: this.level,
+      level: parseInt(this.level.toString()),
       cost: this.cost,
       canRitual: this.canRitual,
       school: this.school,
@@ -185,7 +185,6 @@ export class SpellModalComponent {
       doesDamage: this.doesDamage,
       doesHeal: this.doesHeal,
     };
-    console.log('New Spell from modal: ', spell);
     return spell;
   }
 

+ 130 - 0
src/app/shared-components/full-spellcard/full-spellcard.component.html

@@ -0,0 +1,130 @@
+<div class="full-spellcard-container">
+  <div class="full-spellcard">
+    <h2 style="text-align: center; margin-top: 1rem">{{ spell.name }}</h2>
+
+    <table class="table table-striped" style="height: 20rem; overflow: auto">
+      <thead>
+        <tr>
+          <th scope="col">Property</th>
+          <th scope="col">Value</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td>Komponenten:</td>
+          <td>
+            @if(spell.needsVerbal){Verbal} @if(spell.needsSomatic){, Somatic}
+            @if(spell.needsMaterial){, Material}
+          </td>
+        </tr>
+        <!-- <tr>
+          <td>Benötigt Geste</td>
+          <td>{{ spell.needsSomatic }}</td>
+        </tr>
+        <tr>
+          <td>Benötigt materielle Komponente</td>
+          <td>{{ spell.needsMaterial }}</td>
+        </tr> -->
+
+        <tr>
+          <td>Stufe</td>
+          <td>{{ spell.level }}</td>
+        </tr>
+        <tr>
+          <td>Kosten</td>
+          <td>{{ spell.cost }}</td>
+        </tr>
+        <tr>
+          <td>Kann als Ritual gewirkt werden</td>
+          <td>{{ spell.canRitual }}</td>
+        </tr>
+        <tr>
+          <td>Wirkungsdauer</td>
+          <td>{{ spell.duration }}</td>
+        </tr>
+
+        <tr></tr>
+        <tr>
+          <td>Benötigt Konzentration</td>
+          <td>{{ spell.needsConcentration }}</td>
+        </tr>
+        <tr>
+          <td>Schule</td>
+          <td>{{ spell.school }}</td>
+        </tr>
+        <tr>
+          <td>Reichweite</td>
+          @if (spell.isRanged){
+          <td>{{ spell.range }}</td>
+          } @else {
+          <td>Berührung</td>
+          }
+        </tr>
+        @if (spell.hasAreaOfEffect){
+        <tr>
+          <td>Flächeneffekt</td>
+          <td>{{ spell.areaOfEffectType }}</td>
+        </tr>
+        <tr>
+          <td>Radius</td>
+          <td>{{ spell.radius }}</td>
+        </tr>
+        } @if (spell.needsAttackRoll){
+        <tr>
+          <td>Benötigt Angriffswurf</td>
+          <td>{{ spell.needsAttackRoll }}</td>
+        </tr>
+
+        } @if (spell.needsSavingThrow){
+        <tr>
+          <td>Benötigt Rettungswurf</td>
+          <td>{{ spell.needsSavingThrow }}</td>
+        </tr>
+        <tr>
+          <td>Rettungswurfattribut</td>
+          <td>{{ spell.savingThrowAttribute }}</td>
+        </tr>
+        } @if (spell.doesDamage){
+        <tr>
+          <td>Schaden</td>
+          <td>
+            @for(damage of spell.damage; track damage){
+            {{ damage.diceNumber }} {{ damage.diceType }}
+            {{ damage.damageType }} @if (damage.additionalDamage){ +
+            {{ damage.additionalDamage }}}
+
+            <br />
+            }
+          </td>
+        </tr>
+        } @if (spell.doesHeal){
+        <tr>
+          <td>Heilung</td>
+          <td>
+            {{ spell.heal.diceNumber }} {{ spell.heal.diceType }} @if
+            (spell.heal.additionalHeal){ + {{ spell.heal.additionalHeal }}}
+          </td>
+        </tr>
+        }
+        <h3>Beschreibung</h3>
+        <p>{{ spell.description }}</p>
+      </tbody>
+    </table>
+  </div>
+
+  <div class="delete-row">
+    @if(!isFromDashboard){
+    <button
+      [class]="alreadyInFavorites ? 'disabled add-button' : 'add-button'"
+      (click)="alreadyInFavorites ? '' : addToFavorites()"
+    >
+      @if(alreadyInFavorites){ Bereits in Favoriten} @else{ Zu Favoriten
+      hinzufügen }
+    </button>
+    <button class="edit-button" (click)="update()">Anpassen</button>
+    <button class="delete-button" (click)="delete()">Löschen</button>
+    } @else {
+    <button class="delete-button" (click)="delete()">Entfernen</button>
+    }
+  </div>
+</div>

+ 62 - 0
src/app/shared-components/full-spellcard/full-spellcard.component.scss

@@ -0,0 +1,62 @@
+@mixin button {
+    color: black;
+    border: none;
+    border-radius: 10px;
+    box-shadow: var(--shadow);
+    height: 4rem;
+    width: 8rem;
+    font-size: 1.125rem;
+    font-weight: 600;
+    transition: all 0.2s ease-in-out;
+}
+
+.full-spellcard {
+    height: 42rem;
+    width: 30rem;
+    border: solid 1px var(--border-color);
+    border-radius: 10px;
+    box-shadow: var(--shadow);
+    background: white;
+    overflow: auto;
+}
+
+.delete-row {
+    display: flex;
+    justify-content: space-around;
+    margin-top: 1rem;
+}
+.delete-button {
+    background: var(--delete);
+    @include button;
+    &:hover {
+        background: var(--delete-hover);
+        scale: 1.03;
+    }
+}
+
+.edit-button {
+    background: var(--edit);
+    @include button;
+    &:hover {
+        background: var(--edit-hover);
+        scale: 1.03;
+    }
+}
+
+.add-button {
+    background: var(--accept);
+    @include button;
+    &:hover {
+        background: var(--accept-hover);
+        scale: 1.03;
+    }
+
+    &.disabled {
+        filter: grayscale(50%);
+        &:hover {
+            scale: 1;
+            background: var(--accept);
+            cursor: default;
+        }
+    }
+}

+ 23 - 0
src/app/shared-components/full-spellcard/full-spellcard.component.spec.ts

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

+ 28 - 0
src/app/shared-components/full-spellcard/full-spellcard.component.ts

@@ -0,0 +1,28 @@
+import { Component, Input } from '@angular/core';
+import { Spell } from 'src/interfaces/spell';
+import { ModalService } from 'src/services/modal/modal.service';
+
+@Component({
+  selector: 'app-full-spellcard',
+  templateUrl: './full-spellcard.component.html',
+  styleUrl: './full-spellcard.component.scss',
+})
+export class FullSpellcardComponent {
+  @Input() public spell!: Spell;
+  @Input() public isFromDashboard!: boolean;
+  @Input() public alreadyInFavorites!: boolean;
+
+  public constructor(private modalAccessor: ModalService) {}
+
+  public delete(): void {
+    this.modalAccessor.handleModalClosing('delete', undefined);
+  }
+
+  public update(): void {
+    this.modalAccessor.handleModalClosing('update', undefined);
+  }
+
+  public addToFavorites(): void {
+    this.modalAccessor.handleModalClosing('add', undefined);
+  }
+}

+ 2 - 1
src/app/shared-components/shared-components.module.ts

@@ -2,9 +2,10 @@ import { NgModule } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { SwitchComponent } from './switch/switch.component';
 import { UiButtonComponent } from './ui-button/ui-button.component';
+import { FullSpellcardComponent } from './full-spellcard/full-spellcard.component';
 
 @NgModule({
-  declarations: [SwitchComponent, UiButtonComponent],
+  declarations: [SwitchComponent, UiButtonComponent, FullSpellcardComponent],
   imports: [CommonModule],
   exports: [SwitchComponent, UiButtonComponent],
 })

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

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z"/></svg>

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

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z"/></svg>

BIN
src/assets/images/spells/add-spell.jpg


BIN
src/assets/images/spells/guidance.jpg


+ 19 - 11
src/index.html

@@ -1,13 +1,21 @@
-<!doctype html>
+<!DOCTYPE html>
 <html lang="en">
-<head>
-  <meta charset="utf-8">
-  <title>DnDTools</title>
-  <base href="/">
-  <meta name="viewport" content="width=device-width, initial-scale=1">
-  <link rel="icon" type="image/x-icon" href="favicon.ico">
-</head>
-<body>
-  <app-root></app-root>
-</body>
+  <head>
+    <meta charset="utf-8" />
+    <title>DnDTools</title>
+    <base href="/" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <link rel="icon" type="image/x-icon" href="favicon.ico" />
+    <!-- <link
+      href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap"
+      rel="stylesheet"
+    /> -->
+    <link
+      href="https://fonts.googleapis.com/icon?family=Material+Icons"
+      rel="stylesheet"
+    />
+  </head>
+  <body>
+    <app-root></app-root>
+  </body>
 </html>

+ 1 - 1
src/interfaces/ability.ts

@@ -1,7 +1,7 @@
 export interface Ability {
   name: string;
   shortDescription: string;
-  longDescription?: string;
+  longDescription: string;
   cost: string;
   charges: number;
   currentlyUsedCharges: number;

+ 12 - 0
src/interfaces/spell.ts

@@ -2,6 +2,7 @@ export interface Spell {
   name: string;
   level: number;
   cost: string;
+  duration: number;
   canRitual: boolean;
   needsConcentration: boolean;
   needsVerbal: boolean;
@@ -25,6 +26,15 @@ export interface Spell {
   heal: Heal;
 }
 
+// Additions
+
+// Number of targets
+// duration
+// cost um 1 minute/10 minutes/1 hour/8 hours/24 hours zu casten erhöhen
+// Material
+// Better way of handling the text.
+// come up with a way to mark that it is selected as a ritual
+
 interface Damage {
   diceNumber: string;
   diceType: string;
@@ -32,6 +42,8 @@ interface Damage {
   additionalDamage?: string;
 }
 
+// additions
+
 interface Heal {
   diceNumber: string;
   diceType: string;

+ 2 - 2
src/interfaces/weapon.ts

@@ -2,8 +2,8 @@ export interface Weapon {
   name: string;
   damage: Damage[];
   attackBonus: string;
-  range: string;
-  throwRange?: string;
+  range: number[];
+  throwRange?: number[];
   proficient: boolean;
   isVersatile: boolean;
   isTwoHanded: boolean;

+ 417 - 886
src/services/data/data.service.ts

@@ -1,6 +1,5 @@
 import { Injectable } from '@angular/core';
 import Localbase from 'localbase';
-import { Character } from 'src/interfaces/character';
 import { Router } from '@angular/router';
 import { BehaviorSubject } from 'rxjs';
 import { Attribute } from 'src/interfaces/attribute';
@@ -16,296 +15,120 @@ import { Food } from 'src/interfaces/food';
   providedIn: 'root',
 })
 export class DataService {
-  db: any;
+  private db: any;
+
+  public dataLoaded = false;
+  public characterName: string = '';
 
   constructor(private Router: Router) {
-    this.db = new Localbase('myDatabase');
-    // this.test();
-  }
-
-  // public test(): void {
-  //   let char: Character = {
-  //     characterData: {
-  //       name: 'Eliane',
-  //       class: '',
-  //       race: '',
-  //       level: '',
-  //       subclass: '',
-  //       background: '',
-  //       experience: '',
-  //       inspiration: '',
-  //       proficiencyBonus: '2',
-  //     },
-  //     attributes: {
-  //       strength: ['12', false],
-  //       dexterity: ['14', false],
-  //       constitution: ['16', false],
-  //       intelligence: ['10', false],
-  //       wisdom: ['16', false],
-  //       charisma: ['10', false],
-  //     },
-  //     skills: {
-  //       acrobatics: ['12', false],
-  //       animalHandling: ['11', false],
-  //       arcana: ['11', false],
-  //       athletics: ['11', false],
-  //       deception: ['11', false],
-  //       history: ['12', false],
-  //       insight: ['13', false],
-  //       intimidation: ['14', false],
-  //       investigation: ['16', false],
-  //       medicine: ['15', false],
-  //       nature: ['17', false],
-  //       perception: ['18', false],
-  //       performance: ['19', false],
-  //       persuasion: ['20', false],
-  //       religion: ['9', false],
-  //       sleightOfHand: ['8', false],
-  //       stealth: ['7', false],
-  //       survival: ['6', false],
-  //     },
-  //     combatStats: {
-  //       armorClass: '18',
-  //       initiative: '+2',
-  //       speed: '30',
-  //       hitPointMaximum: '12',
-  //       currentHitPoints: '12',
-  //       temporaryHitPoints: '0',
-  //       hitDice: '',
-  //       deathSaveSuccesses: '0',
-  //       deathSaveFailures: '0',
-  //     },
-  //     appearance: {
-  //       age: '',
-  //       height: '',
-  //       weight: '',
-  //       eyes: '',
-  //       skin: '',
-  //       hair: '',
-  //     },
-  //     personality: {
-  //       personalityTraits: '',
-  //       ideals: '',
-  //       bonds: '',
-  //       flaws: '',
-  //     },
-  //     weapons: {
-  //       name: '',
-  //       attackBonus: '',
-  //       damage: '',
-  //       damageType: '',
-  //       range: '',
-  //       description: '',
-  //     },
-  //   };
-  //   this.character = char;
-  //   // console.log('character: ', this.character);
-  // }
-
-  public character: any = {};
-
-  // #region character data retrieval
-
-  public getCompleteCharacter(): Character {
-    return this.character;
-  }
-
-  public getCharacterData(): any {
-    return this.character.characterData;
-  }
-
-  public getAppearance(): any {
-    return this.character.appearance;
-  }
-
-  public getAttributes(): any {
-    return this.character.attributes;
-  }
-
-  public getSkills(): any {
-    return this.character.skills;
-  }
-
-  public getCombatStats(): any {
-    return this.character.combatStats;
-  }
-
-  public getPersonality(): any {
-    return this.character.personality;
-  }
-
-  // public getWeapons(): any {
-  //   return this.character.weapons;
-  // }
+    this.db = new Localbase('DNDTools');
+    // this.db.config.debug = false;
+  }
+
+  async loadData(): Promise<any> {
+    this.characterName = sessionStorage.getItem('characterName')!;
+    if (this.dataLoaded) {
+      return Promise.resolve();
+    }
+    return this.getCollectionWithKeys(this.characterName).then((data: any) => {
+      this.buildCurrentCharacter(data);
+      this.dataLoaded = true;
+    });
+  }
+
+  // #region character selection and construction
+
+  private buildCurrentCharacter(currentCharacterData: any): void {
+    const [
+      abilitiesData,
+      appearanceData,
+      attributesData,
+      characterData,
+      combatStatsData,
+      consumablesData,
+      favoriteSpellsData,
+      favoriteWeaponsData,
+      foodData,
+      hitPointsData,
+      kiPointsData,
+      miscellaneousData,
+      moneyData,
+      personalityData,
+      proficienciesData,
+      skillsData,
+      spellLevel0,
+      spellLevel1,
+      spellLevel2,
+      spellLevel3,
+      spellLevel4,
+      spellLevel5,
+      spellLevel6,
+      spellLevel7,
+      spellLevel8,
+      spellLevel9,
+      spellslotsData,
+      traitsData,
+      weaponsAndArmorData,
+    ] = currentCharacterData.map((entry: any) => entry.data);
+
+    // Attributes
+    Object.keys(attributesData).forEach((key: string) => {
+      this.updateAttribute(attributesData[key]);
+    });
+
+    //  Skills
+    Object.keys(skillsData).forEach((key: string) => {
+      this.updateSkill(skillsData[key]);
+    });
+
+    // Hit Points
+    this.hitPoints = hitPointsData.hitPoints;
+    this.hitDice = hitPointsData.hitDice;
+
+    // Combat Stats
+    this.armorClass = combatStatsData.armorClass;
+    this.initiative = combatStatsData.initiative;
+    this.movement = combatStatsData.movement;
+    this.updateProficiencyBonus(combatStatsData.proficiencyBonus);
+    this.deathSaves = combatStatsData.deathSaves;
+    this.conditions = combatStatsData.conditions;
+    this.exhaustion = combatStatsData.exhaustion;
+    this.inspiration = combatStatsData.inspiration;
+
+    // Spells
+    this.spellslots = spellslotsData;
+    this.favoriteSpells = favoriteSpellsData.spells;
+    this.spellLevel0 = spellLevel0.spells;
+    this.spellLevel1 = spellLevel1.spells;
+    this.spellLevel2 = spellLevel2.spells;
+    this.spellLevel3 = spellLevel3.spells;
+    this.spellLevel4 = spellLevel4.spells;
+    this.spellLevel5 = spellLevel5.spells;
+    this.spellLevel6 = spellLevel6.spells;
+    this.spellLevel7 = spellLevel7.spells;
+    this.spellLevel8 = spellLevel8.spells;
+    this.spellLevel9 = spellLevel9.spells;
+
+    // Items
+    this.favoriteWeapons = favoriteWeaponsData.data;
+    this.weaponsAndArmor = weaponsAndArmorData.data;
+    this.consumables = consumablesData.data;
+    this.miscellaneous = miscellaneousData.data;
+    this.food = foodData.data;
+    this.money = moneyData;
+
+    // Abilities and stuff
+    this.kiPoints = kiPointsData;
+    this.traits = traitsData.data;
+    this.abilities = abilitiesData.data;
+    this.proficiencies = proficienciesData;
+  }
 
   // #endregion
 
-  // #region data storage
-
-  private weapons: Weapon[] = [
-    {
-      name: 'Großschwert',
-      attackBonus: '+3',
-      damage: [{ diceNumber: '2', diceType: 'd6', damageType: 'slashing' }],
-      proficient: true,
-      range: '5ft',
-      throwRange: '',
-      isFinesse: false,
-      isVersatile: false,
-      isTwoHanded: true,
-      isRanged: false,
-      canBeThrown: false,
-      weight: 'schwer',
-      isMagical: false,
-    },
-    {
-      name: 'Kurzbogen +1',
-      attackBonus: '+5',
-      damage: [{ diceNumber: '1', diceType: 'd8', damageType: 'piercing' }],
-      proficient: false,
-      range: '80/320ft',
-      throwRange: '',
-      isFinesse: false,
-      isVersatile: false,
-      isTwoHanded: true,
-      isRanged: true,
-      canBeThrown: false,
-      weight: 'leicht',
-      isMagical: true,
-      magicBonus: 1,
-    },
-    {
-      name: 'Feuerstab',
-      attackBonus: '+5',
-      damage: [
-        { diceNumber: '1', diceType: 'd6', damageType: 'bludgeoning' },
-        { diceNumber: '1', diceType: 'd4', damageType: 'fire' },
-      ],
-      versatileDamage: 'd8',
-      proficient: true,
-      range: '5ft',
-      throwRange: '',
-      isFinesse: false,
-      isVersatile: true,
-      isTwoHanded: false,
-      isRanged: false,
-      canBeThrown: false,
-      weight: 'normal',
-      isMagical: false,
-    },
-  ];
-
-  public getWeapons(): Weapon[] {
-    return this.weapons;
-  }
-
-  public setWeapons(weapons: Weapon[]): void {
-    this.weapons = weapons;
-    console.log('weapons updated from data service: ', this.weapons);
-  }
-
-  private _favoriteSpells: Spell[] = [
-    {
-      name: 'Feuerball',
-      level: 3,
-      cost: 'action',
-      canRitual: false,
-      needsVerbal: true,
-      needsSomatic: true,
-      needsMaterial: true,
-      needsConcentration: false,
-      needsAttackRoll: false,
-      needsSavingThrow: true,
-      savingThrowAttribute: 'dexterity',
-      school: 'evocation',
-      doesDamage: true,
-      damage: [
-        { diceNumber: '8', diceType: 'd6', damageType: 'fire' },
-        { diceNumber: '8', diceType: 'd6', damageType: 'fire' },
-      ],
-      doesHeal: false,
-      heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-      isRanged: true,
-      description:
-        'Ein heller Strahl schießt von deinem Zeigefinger zu einem von dir gewählten Punkt in Reichweite und blüht dann mit einem tiefen Brüllen zu einer Flammenexplosion auf. Jede Kreatur in einem Umkreis von 6 Metern um diesen Punkt muss einen Rettungswurf auf Geschicklichkeit machen. Bei einem misslungenen Rettungswurf erleidet das Ziel 8W6 Feuerschaden, bei einem erfolgreichen Wurf die Hälfte des Schadens. Das Feuer breitet sich um Ecken aus. Es entzündet brennbare Gegenstände in der Umgebung, die nicht getragen werden. Wenn du diesen Zauber mit einem Zauberplatz des 4. Grades oder höher wirkst, erhöht sich der Schaden um 1W6 für jeden Grad über dem 3.',
-      range: 150,
-      hasAreaOfEffect: true,
-      radius: 20,
-      areaOfEffectType: 'sphere',
-    },
-    {
-      name: 'Feuerpfeil',
-      level: 0,
-      cost: 'action',
-      canRitual: false,
-      needsVerbal: true,
-      needsSomatic: true,
-      needsMaterial: true,
-      needsConcentration: false,
-      needsAttackRoll: true,
-      needsSavingThrow: false,
-      school: 'evocation',
-      doesDamage: true,
-      doesHeal: false,
-      heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-      description:
-        'Du schleuderst einen Splitter aus Feuer auf eine Kreatur in Reichweite. Lege einen Fernkampf-Zauberangriff gegen das Ziel ab. Bei einem Treffer erleidet das Ziel 1W10 Feuerschaden. Ein brennbarer Gegenstand, der von diesem Zauber getroffen wird, geht in Flammen auf, wenn er nicht getragen oder in der Hand gehalten wird. Der Schaden dieses Zaubers steigt um 1W10, wenn du die 5. Stufe (2W10), die 11. Stufe (3W10) und die 17. Stufe (4W10) erreichst.  ',
-      damage: [{ diceNumber: '1', diceType: 'd10', damageType: 'fire' }],
-      isRanged: true,
-      range: 120,
-      hasAreaOfEffect: false,
-      areaOfEffectType: '',
-      radius: 0,
-    },
-    {
-      name: 'Vampiric Touch',
-      level: 3,
-      cost: 'action',
-      canRitual: false,
-      needsVerbal: true,
-      needsSomatic: true,
-      needsMaterial: false,
-      needsConcentration: true,
-      needsAttackRoll: true,
-      needsSavingThrow: false,
-      doesDamage: true,
-      doesHeal: false,
-      heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-      description:
-        'Die Berührung deiner schattenumrankten Hand kann anderen die Lebenskraft entziehen, um deine Wunden zu heilen. Führe einen Nahkampf-Zauberangriff gegen eine Kreatur in deiner Reichweite aus. Bei einem Treffer erleidet das Ziel 3W6 nekrotischen Schaden, und du erhältst Trefferpunkte in Höhe der Hälfte des zugefügten nekrotischen Schadens zurück. Bis der Zauber endet, kannst du den Angriff in jeder deiner Runden als Aktion wiederholen. Wenn du diesen Zauber mit einem Zauberplatz des 4. Grades oder höher wirkst, erhöht sich der Schaden um 1W6 für jeden Grad über dem 3.',
-      school: 'necromancy',
-      damage: [{ diceNumber: '3', diceType: 'd6', damageType: 'necrotic' }],
-      isRanged: false,
-      range: 5,
-      hasAreaOfEffect: false,
-      areaOfEffectType: '',
-      radius: 0,
-    },
-    {
-      name: 'Heilende Berührung',
-      level: 1,
-      cost: 'action',
-      canRitual: false,
-      needsVerbal: true,
-      needsSomatic: true,
-      needsMaterial: false,
-      needsConcentration: false,
-      needsAttackRoll: false,
-      needsSavingThrow: false,
-      doesDamage: false,
-      damage: [{ diceNumber: '', diceType: '', damageType: '' }],
-      doesHeal: true,
-      heal: { diceNumber: '1', diceType: 'd8', additionalHeal: 5 },
-      description:
-        'Deine Berührung kann Wunden heilen. Berühre eine Kreatur und spende ihr 1W8 + deinem Fertigkeitsbonus Trefferpunkte. Dieser Zauber hat keine Wirkung auf Untote oder Konstrukte. Wenn du diesen Zauber mit einem Zauberplatz des 2. Grades oder höher wirkst, erhöht sich die Heilung um 1W8 für jeden Grad über dem 1.',
-      school: 'evocation',
-      isRanged: false,
-      range: 5,
-      hasAreaOfEffect: false,
-      areaOfEffectType: '',
-      radius: 0,
-    },
-  ];
+  // #region # SPELLS
+
+  private _favoriteSpells: Spell[] = [];
 
   public get favoriteSpells(): Spell[] {
     return this._favoriteSpells;
@@ -313,84 +136,15 @@ export class DataService {
 
   public set favoriteSpells(spells: Spell[]) {
     this._favoriteSpells = spells;
-    console.log('spells updated from data service: ', this._favoriteSpells);
-  }
-
-  private _spellLevel0: Spell[] = [
-    {
-      name: 'Führung',
-      level: 0,
-      cost: 'action',
-      canRitual: false,
-      needsVerbal: true,
-      needsSomatic: true,
-      needsMaterial: true,
-      needsConcentration: true,
-      needsAttackRoll: false,
-      needsSavingThrow: false,
-      doesDamage: false,
-      damage: [{ diceNumber: '', diceType: '', damageType: '' }],
-      doesHeal: false,
-      heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-      description:
-        'Du berührst eine Kreatur und bis zum Ende des Zuges erhält sie einen Bonus von 1W4 auf Angriffs- und Rettungswürfe.',
-      school: 'enchantment',
-      isRanged: false,
-      range: 5,
-      hasAreaOfEffect: false,
-      areaOfEffectType: '',
-      radius: 0,
-    },
-    {
-      name: 'Totenläuten',
-      level: 0,
-      cost: 'action',
-      canRitual: false,
-      needsVerbal: true,
-      needsSomatic: false,
-      needsMaterial: true,
-      needsConcentration: false,
-      needsAttackRoll: false,
-      needsSavingThrow: true,
-      savingThrowAttribute: 'wisdom',
-      doesDamage: true,
-      damage: [{ diceNumber: '1', diceType: 'd8', damageType: 'necrotic' }],
-      doesHeal: false,
-      heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-      description:
-        'Eine Kreatur, die du berührst, erleidet 1W8 Nekrotischen Schaden, wenn sie einen Rettungswurf auf Konstitution nicht besteht.',
-      school: 'necromancy',
-      isRanged: true,
-      range: 60,
-      hasAreaOfEffect: false,
-      areaOfEffectType: '',
-      radius: 0,
-    },
-    {
-      name: 'Thaumaturgie',
-      level: 0,
-      cost: 'action',
-      canRitual: false,
-      needsVerbal: true,
-      needsSomatic: true,
-      needsMaterial: false,
-      needsConcentration: false,
-      needsAttackRoll: false,
-      needsSavingThrow: false,
-      doesDamage: false,
-      damage: [{ diceNumber: '', diceType: '', damageType: '' }],
-      doesHeal: false,
-      heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-      description:
-        'Du öffnest eine Tür, entzündest eine Kerze oder machst ein Geräusch, das du innerhalb der Reichweite deiner Stimme erzeugen kannst. Du kannst auch deine Stimme bis zum Ende deines nächsten Zuges um das Dreifache verstärken.',
-      school: 'transmutation',
-      isRanged: false,
-      range: 30,
-      hasAreaOfEffect: false,
-      areaOfEffectType: '',
-      radius: 0,
-    },
-  ];
+    this.setData('favoriteSpells', { spells: spells });
+  }
+
+  public addFavoriteSpell(spell: Spell): void {
+    this._favoriteSpells.push(spell);
+    this.setData('favoriteSpells', { spells: this._favoriteSpells });
+  }
+
+  private _spellLevel0: Spell[] = [];
 
   public get spellLevel0(): Spell[] {
     return this._spellLevel0;
@@ -398,59 +152,10 @@ export class DataService {
 
   public set spellLevel0(spells: Spell[]) {
     this._spellLevel0 = spells;
-    console.log('spells updated from data service: ', this._spellLevel0);
-  }
-
-  private _spellLevel1: Spell[] = [
-    {
-      name: 'Heilende Berührung',
-      level: 1,
-      cost: 'action',
-      canRitual: false,
-      needsVerbal: true,
-      needsSomatic: true,
-      needsMaterial: false,
-      needsConcentration: false,
-      needsAttackRoll: false,
-      needsSavingThrow: false,
-      doesDamage: false,
-      damage: [{ diceNumber: '', diceType: '', damageType: '' }],
-      doesHeal: true,
-      heal: { diceNumber: '1', diceType: 'd8', additionalHeal: 5 },
-      description:
-        'Deine Berührung kann Wunden heilen. Berühre eine Kreatur und spende ihr 1W8 + deinem Fertigkeitsbonus Trefferpunkte. Dieser Zauber hat keine Wirkung auf Untote oder Konstrukte. Wenn du diesen Zauber mit einem Zauberplatz des 2. Grades oder höher wirkst, erhöht sich die Heilung um 1W8 für jeden Grad über dem 1.',
-      school: 'evocation',
-      isRanged: false,
-      range: 5,
-      hasAreaOfEffect: false,
-      areaOfEffectType: '',
-      radius: 0,
-    },
-    {
-      name: 'Magie entdecken',
-      level: 1,
-      cost: 'action',
-      canRitual: true,
-      needsVerbal: true,
-      needsSomatic: true,
-      needsMaterial: true,
-      needsConcentration: false,
-      needsAttackRoll: false,
-      needsSavingThrow: false,
-      doesDamage: false,
-      damage: [{ diceNumber: '', diceType: '', damageType: '' }],
-      doesHeal: false,
-      heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-      description:
-        'Dein Blick wird magisch, um magische Kräfte zu erkennen. Für die Dauer erfährst du die Anzahl und die Art aller magischen Kräfte, die du innerhalb von 30 Fuß wahrnehmen kannst. Du erfährst auch die Schule der Magie, falls vorhanden.',
-      school: 'divination',
-      isRanged: false,
-      range: 30,
-      hasAreaOfEffect: false,
-      areaOfEffectType: '',
-      radius: 0,
-    },
-  ];
+    this.setData('spellLevel0', { spells: spells });
+  }
+
+  private _spellLevel1: Spell[] = [];
 
   public get spellLevel1(): Spell[] {
     return this._spellLevel1;
@@ -458,63 +163,10 @@ export class DataService {
 
   public set spellLevel1(spells: Spell[]) {
     this._spellLevel1 = spells;
-    console.log('spells updated from data service: ', this._spellLevel1);
-  }
-
-  private _spellLevel2: Spell[] = [
-    {
-      name: 'Feuerball',
-      level: 2,
-      cost: 'action',
-      canRitual: false,
-      needsVerbal: true,
-      needsSomatic: true,
-      needsMaterial: true,
-      needsConcentration: false,
-      needsSavingThrow: true,
-      needsAttackRoll: false,
-      savingThrowAttribute: 'dexterity',
-      school: 'evocation',
-      doesDamage: true,
-      damage: [
-        { diceNumber: '8', diceType: 'd6', damageType: 'fire' },
-        { diceNumber: '8', diceType: 'd6', damageType: 'fire' },
-      ],
-      doesHeal: false,
-      heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-      isRanged: true,
-      description:
-        'Ein heller Strahl schießt von deinem Zeigefinger zu einem von dir gewählten Punkt in Reichweite und blüht dann mit einem tiefen Brüllen zu einer Flammenexplosion auf. Jede Kreatur in einem Umkreis von 6 Metern um diesen Punkt muss einen Rettungswurf auf Geschicklichkeit machen. Bei einem misslungenen Rettungswurf erleidet das Ziel 8W6 Feuerschaden, bei einem erfolgreichen Wurf die Hälfte des Schadens. Das Feuer breitet sich um Ecken aus. Es entzündet brennbare Gegenstände in der Umgebung, die nicht getragen werden. Wenn du diesen Zauber mit einem Zauberplatz des 4. Grades oder höher wirkst, erhöht sich der Schaden um 1W6 für jeden Grad über dem 3.',
-      range: 150,
-      hasAreaOfEffect: true,
-      radius: 20,
-      areaOfEffectType: 'sphere',
-    },
-    {
-      name: 'Vampiric Touch',
-      level: 3,
-      cost: 'action',
-      canRitual: false,
-      needsVerbal: true,
-      needsSomatic: true,
-      needsMaterial: false,
-      needsConcentration: true,
-      needsSavingThrow: false,
-      needsAttackRoll: true,
-      doesDamage: true,
-      damage: [{ diceNumber: '3', diceType: 'd6', damageType: 'necrotic' }],
-      doesHeal: false,
-      heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
-      description:
-        'Die Berührung deiner schattenumrankten Hand kann anderen die Lebenskraft entziehen, um deine Wunden zu heilen. Führe einen Nahkampf-Zauberangriff gegen eine Kreatur in deiner Reichweite aus. Bei einem Treffer erleidet das Ziel 3W6 nekrotischen Schaden, und du erhältst Trefferpunkte in Höhe der Hälfte des zugefügten nekrotischen Schadens zurück. Bis der Zauber endet, kannst du den Angriff in jeder deiner Runden als Aktion wiederholen. Wenn du diesen Zauber mit einem Zauberplatz des 4. Grades oder höher wirkst, erhöht sich der Schaden um 1W6 für jeden Grad über dem 3.',
-      school: 'necromancy',
-      isRanged: false,
-      range: 5,
-      hasAreaOfEffect: false,
-      areaOfEffectType: '',
-      radius: 0,
-    },
-  ];
+    this.setData('spellLevel1', { spells: spells });
+  }
+
+  private _spellLevel2: Spell[] = [];
 
   public get spellLevel2(): Spell[] {
     return this._spellLevel2;
@@ -522,198 +174,199 @@ export class DataService {
 
   public set spellLevel2(spells: Spell[]) {
     this._spellLevel2 = spells;
-    console.log('spells updated from data service: ', this._spellLevel2);
-  }
-
-  private abilities: Ability[] = [
-    {
-      name: 'Feenschritt',
-      charges: 8,
-      currentlyUsedCharges: 4,
-      cost: 'bonus',
-      shortDescription:
-        'Als Bonusaktion kannst du dich bis zu 30 Fuß zu einem Punkt teleportieren, den du sehen kannst.',
-      longDescription:
-        'Als Bonusaktion kannst du dich auf magische Weise bis zu 30 Fuß weit in einen unbesetzten Raum teleportieren, den du sehen kannst. Du kannst diese Eigenschaft so oft einsetzen, wie es deinem Fertigkeitsbonus entspricht, und du erhältst alle verbrauchten Einsätze zurück, wenn du eine lange Pause machst.',
-    },
-    {
-      name: 'Durchschnaufen',
-      charges: 1,
-      currentlyUsedCharges: 0,
-      cost: 'bonus',
-      shortDescription:
-        'Als Bonusaktion kannst du dich um 1 d10 + Fighter Level Trefferpunkten heilen.',
-      longDescription:
-        'Als Bonusaktion kannst du dich um 1 d10 + Fighter Level Trefferpunkten heilen. Du kannst diese Fähigkeit einmal pro langer Rast einsetzen.',
-    },
-    {
-      name: 'Tatendrang',
-      charges: 1,
-      currentlyUsedCharges: 1,
-      cost: 'none',
-      shortDescription: 'Du kannst eine weitere Aktion ausführen.',
-      longDescription:
-        'Du kannst eine weitere Aktion ausführen. Du kannst diese Fähigkeit einmal pro langer Rast einsetzen.',
-    },
-  ];
-
-  public getAbilities(): Ability[] {
-    return this.abilities;
-  }
-
-  public setSAbilities(abilities: Ability[]): void {
-    this.abilities = abilities;
-    console.log('abilities updated from data service: ', this.abilities);
-  }
-
-  private traits: Trait[] = [
-    {
-      name: 'Feenblut',
-      shortDescription:
-        'Du hast Vorteil auf alle Rettungswürfe gegen Zauber und andere magische Effekte.',
-      longDescription:
-        'Du hast Vorteil auf alle Rettungswürfe gegen Zauber und andere magische Effekte.',
-      origin: 'Elf',
-    },
-    {
-      name: 'Dunkelsicht',
-      shortDescription: 'Du hast 60 ft. Dunkelsicht.',
-      longDescription:
-        'Du hast 60 ft. Dunkelsicht. Du kannst in völliger Dunkelheit so klar sehen wie am Tag.',
-      origin: 'Elf',
-    },
-    {
-      name: 'Fey Ancestry',
-      shortDescription:
-        'Du meditierst und musst nicht schlafen. Du kannst nicht durch Magie in Schlaf versetzt werden.',
-      longDescription:
-        'Du meditierst und musst nicht schlafen. Du kannst nicht durch Magie in Schlaf versetzt werden.',
-      origin: 'Elf',
-    },
-  ];
-
-  public getTraits(): Trait[] {
-    return this.traits;
-  }
-
-  public setTraits(traits: Trait[]): void {
-    this.traits = traits;
-    console.log('traits updated from data service: ', this.traits);
-  }
-
-  private spellslots: any = {
-    spellslots: [
-      { totalSlots: 4, usedSlots: 2 },
-      { totalSlots: 2, usedSlots: 1 },
-    ],
+    this.setData('spellLevel2', { spells: spells });
+  }
+
+  private _spellLevel3: Spell[] = [];
+
+  public get spellLevel3(): Spell[] {
+    return this._spellLevel3;
+  }
+
+  public set spellLevel3(spells: Spell[]) {
+    this._spellLevel3 = spells;
+    this.setData('spellLevel3', { spells: spells });
+  }
+
+  private _spellLevel4: Spell[] = [];
+
+  public get spellLevel4(): Spell[] {
+    return this._spellLevel4;
+  }
+
+  public set spellLevel4(spells: Spell[]) {
+    this._spellLevel4 = spells;
+    this.setData('spellLevel4', { spells: spells });
+  }
+
+  private _spellLevel5: Spell[] = [];
+
+  public get spellLevel5(): Spell[] {
+    return this._spellLevel5;
+  }
+
+  public set spellLevel5(spells: Spell[]) {
+    this._spellLevel5 = spells;
+    this.setData('spellLevel5', { spells: spells });
+  }
+
+  private _spellLevel6: Spell[] = [];
+
+  public get spellLevel6(): Spell[] {
+    return this._spellLevel6;
+  }
+
+  public set spellLevel6(spells: Spell[]) {
+    this._spellLevel6 = spells;
+    this.setData('spellLevel6', { spells: spells });
+  }
+
+  private _spellLevel7: Spell[] = [];
+
+  public get spellLevel7(): Spell[] {
+    return this._spellLevel7;
+  }
+
+  public set spellLevel7(spells: Spell[]) {
+    this._spellLevel7 = spells;
+    this.setData('spellLevel7', { spells: spells });
+  }
+
+  private _spellLevel8: Spell[] = [];
+
+  public get spellLevel8(): Spell[] {
+    return this._spellLevel8;
+  }
+
+  public set spellLevel8(spells: Spell[]) {
+    this._spellLevel8 = spells;
+    this.setData('spellLevel8', { spells: spells });
+  }
+
+  private _spellLevel9: Spell[] = [];
+
+  public get spellLevel9(): Spell[] {
+    return this._spellLevel9;
+  }
+
+  public set spellLevel9(spells: Spell[]) {
+    this._spellLevel9 = spells;
+    this.setData('spellLevel9', { spells: spells });
+  }
+
+  public getAllPreparedSpells(): Spell[] {
+    return [
+      ...this._spellLevel0,
+      ...this._spellLevel1,
+      ...this._spellLevel2,
+      ...this._spellLevel3,
+      ...this._spellLevel4,
+      ...this._spellLevel5,
+      ...this._spellLevel6,
+      ...this._spellLevel7,
+      ...this._spellLevel8,
+      ...this._spellLevel9,
+    ];
+  }
+
+  // #endregion
+
+  // #region ABILITIES
+
+  private _abilities: Ability[] = [];
+
+  public get abilities(): Ability[] {
+    return this._abilities;
+  }
+
+  public set abilities(abilities: Ability[]) {
+    this._abilities = abilities;
+    this.setData('abilities', { data: abilities });
+  }
+
+  private _traits: Trait[] = [];
+
+  public get traits(): Trait[] {
+    return this._traits;
+  }
+
+  public set traits(traits: Trait[]) {
+    this._traits = traits;
+    this.setData('traits', { data: traits });
+  }
+
+  private _spellslots: any = {
+    spellslots: [],
     showSpellslots: true,
   };
 
-  public getSpellslots(): any {
-    return this.spellslots;
+  public get spellslots(): any {
+    return this._spellslots;
   }
 
-  public setSpellslots(spellslots: any): void {
-    this.spellslots = spellslots;
-    console.log('spellslots updated from data service: ', this.spellslots);
+  public set spellslots(spellslots: any) {
+    this._spellslots = spellslots;
+    this.setData('spellslots', spellslots);
   }
 
-  private kiPoints: any = {
+  private _kiPoints: any = {
     totalPoints: 4,
     usedPoints: 2,
-    showKiPoints: true,
+    showKiPoints: false,
   };
 
-  public getKiPoints(): any {
-    return this.kiPoints;
+  public get kiPoints(): any {
+    return this._kiPoints;
   }
 
-  public setKiPoints(kiPoints: any): void {
-    this.kiPoints = kiPoints;
-    console.log('kiPoints updated from data service: ', this.kiPoints);
+  public set kiPoints(kiPoints: any) {
+    this._kiPoints = kiPoints;
+    this.setData('kiPoints', kiPoints);
   }
 
-  private proficiencies: any = {
-    armor: {
-      light: true,
-      medium: true,
-      heavy: false,
-    },
-    weapons: {
-      simple: true,
-      martial: true,
-      other: ['Kurzschwert', 'Gleve'],
-    },
-    tools: ['Schmiedewerkzeuge', 'Würfel'],
-    languages: ['Gemeinsprache', 'Elfisch', 'Zwergisch'],
-  };
+  private _proficiencies: any = {};
 
-  public getProficiencies(): any {
-    return this.proficiencies;
+  public get proficiencies(): any {
+    return this._proficiencies;
   }
 
-  public setProficiencies(proficiencies: any): void {
-    this.proficiencies = proficiencies;
-    console.log(
-      'proficiencies updated from data service: ',
-      this.proficiencies
-    );
+  public set proficiencies(proficiencies: any) {
+    this._proficiencies = proficiencies;
+    this.setData('proficiencies', proficiencies);
   }
 
-  // #region Character Data observables
+  // #endregion
 
-  /**
-   *
-   * @param newValue The new value of the Character Data
-   */
-  public updateCharacterData(newValue: any) {
-    const functionCall: string = newValue.name + 'Subject.next(newValue)';
-    eval(`this.${functionCall}`);
-    console.log('updateProperty: ', newValue.value);
-    // TODO: Datenbank updaten
-  }
+  // #region # COMBAT STATS
 
-  private proficiencySubject = new BehaviorSubject<any>({
-    name: 'proficiencyBonus',
-    value: 2,
-  });
+  private proficiencySubject = new BehaviorSubject<number>(2);
   public proficiency$ = this.proficiencySubject.asObservable();
 
-  private _hitPoints = {
-    maxHitPoints: 10,
-    currentHitPoints: 8,
-    temporaryHitPoints: 1,
-  };
-
-  public set hitPoints(hitPointsData: any) {
-    console.log('hitPointsData: ', hitPointsData);
-    this._hitPoints = hitPointsData;
+  public updateProficiencyBonus(newValue: number) {
+    this.proficiencySubject.next(newValue);
+    this.writeCombatStatsToDatabase();
   }
 
-  public get hitPoints(): any {
-    return this._hitPoints;
-  }
-
-  private _armorClass: number = 19;
+  private _armorClass: number = 10;
 
   public get armorClass(): number {
     return this._armorClass;
   }
 
   public set armorClass(newValue: number) {
-    console.log('armorClass: ', newValue);
     this._armorClass = newValue;
+    this.writeCombatStatsToDatabase();
   }
 
-  private _initiative: number = 2;
+  private _initiative: number = 0;
 
   public get initiative(): number {
     return this._initiative;
   }
 
   public set initiative(newValue: number) {
-    console.log('initiative: ', newValue);
     this._initiative = newValue;
+    this.writeCombatStatsToDatabase();
   }
 
   private _movement: number = 30;
@@ -723,19 +376,8 @@ export class DataService {
   }
 
   public set movement(newValue: number) {
-    console.log('movement: ', newValue);
     this._movement = newValue;
-  }
-
-  private _hitDice: any[] = [{ diceNumber: '1', diceType: 'd10' }];
-
-  public get hitDice(): any[] {
-    return this._hitDice;
-  }
-
-  public set hitDice(newValue: any[]) {
-    console.log('hitDice: ', newValue);
-    this._hitDice = newValue;
+    this.writeCombatStatsToDatabase();
   }
 
   private _deathSaves: number[] = [0, 0];
@@ -745,8 +387,8 @@ export class DataService {
   }
 
   public set deathSaves(newValue: number[]) {
-    console.log('deathSaves: ', newValue);
     this._deathSaves = newValue;
+    this.writeCombatStatsToDatabase();
   }
 
   private _inspiration: boolean = false;
@@ -756,8 +398,8 @@ export class DataService {
   }
 
   public set inspiration(newValue: boolean) {
-    console.log('inspiration: ', newValue);
     this._inspiration = newValue;
+    this.writeCombatStatsToDatabase();
   }
 
   private _experience: number = 0;
@@ -767,8 +409,8 @@ export class DataService {
   }
 
   public set experience(newValue: number) {
-    console.log('experience: ', newValue);
     this._experience = newValue;
+    this.writeCombatStatsToDatabase();
   }
 
   private _exhaustion: number = 0;
@@ -778,77 +420,152 @@ export class DataService {
   }
 
   public set exhaustion(newValue: number) {
-    console.log('exhaustion: ', newValue);
     this._exhaustion = newValue;
+    this.writeCombatStatsToDatabase();
   }
 
-  private _conditions: string[] = ['Blind', 'Kampfunfähig'];
+  private _conditions: string[] = [];
 
   public get conditions(): string[] {
     return this._conditions;
   }
 
   public set conditions(newValue: string[]) {
-    console.log('conditions: ', newValue);
     this._conditions = newValue;
+    this.writeCombatStatsToDatabase();
+  }
+
+  private writeCombatStatsToDatabase() {
+    const combatStatsData = {
+      armorClass: this._armorClass,
+      initiative: this._initiative,
+      movement: this._movement,
+      deathSaves: this._deathSaves,
+      proficiencyBonus: this.proficiencySubject.getValue(),
+      inspiration: this._inspiration,
+      experience: this._experience,
+      exhaustion: this._exhaustion,
+      conditions: this._conditions,
+    };
+    this.setData('combatStats', combatStatsData);
+  }
+
+  // #endregion
+
+  // #region # HIT POINTS
+
+  private _hitPoints = {
+    maxHitPoints: 6,
+    currentHitPoints: 6,
+    temporaryHitPoints: 1,
+  };
+
+  public get hitPoints(): any {
+    return this._hitPoints;
+  }
+
+  public set hitPoints(newValue: any) {
+    this._hitPoints = newValue;
+    this.updateHitPoints();
+  }
+
+  private _hitDice = {
+    diceNumber: '1',
+    diceType: 'd10',
+  };
+
+  public get hitDice(): any {
+    return this._hitDice;
+  }
+
+  public set hitDice(newValue: any) {
+    this._hitDice = newValue;
+    this.updateHitPoints();
+  }
+
+  private updateHitPoints() {
+    const hitPointsData = {
+      hitPoints: this._hitPoints,
+      hitDice: this._hitDice,
+    };
+    this.setData('hitPoints', hitPointsData);
   }
 
   // #endregion
 
-  /**
-   *
-   * @param newValue The new value of the Attribute
-   */
+  // #region # ATTRIBUTES
   updateAttribute(newValue: Attribute) {
     const functionCall: string = newValue.name + 'Subject.next(newValue)';
     eval(`this.${functionCall}`);
-    console.log('updateAttribute: ', newValue.name);
-    // TODO: Datenbank updaten
+    const attributesData = {
+      strength: this.strengthSubject.getValue(),
+      dexterity: this.dexteritySubject.getValue(),
+      constitution: this.constitutionSubject.getValue(),
+      intelligence: this.intelligenceSubject.getValue(),
+      wisdom: this.wisdomSubject.getValue(),
+      charisma: this.charismaSubject.getValue(),
+    };
+    this.setData('attributes', attributesData);
   }
 
   private strengthSubject = new BehaviorSubject<Attribute>(
-    { name: 'strength', value: 19, proficiency: true } //erstmal nur gemockt, später muss der Wert aus der Datenbank kommen
+    { name: 'strength', value: 10, proficiency: true } //erstmal nur gemockt, später muss der Wert aus der Datenbank kommen
   );
   public strength$ = this.strengthSubject.asObservable();
 
   private dexteritySubject = new BehaviorSubject<Attribute>(
-    { name: 'dexterity', value: 17, proficiency: false } //erstmal nur gemockt, später muss der Wert aus der Datenbank kommen
+    { name: 'dexterity', value: 10, proficiency: false } //erstmal nur gemockt, später muss der Wert aus der Datenbank kommen
   );
   public dexterity$ = this.dexteritySubject.asObservable();
 
   private constitutionSubject = new BehaviorSubject<Attribute>(
-    { name: 'constitution', value: 12, proficiency: false } //erstmal nur gemockt, später muss der Wert aus der Datenbank kommen
+    { name: 'constitution', value: 10, proficiency: false } //erstmal nur gemockt, später muss der Wert aus der Datenbank kommen
   );
   public constitution$ = this.constitutionSubject.asObservable();
 
   private intelligenceSubject = new BehaviorSubject<Attribute>(
-    { name: 'intelligence', value: 13, proficiency: false } //erstmal nur gemockt, später muss der Wert aus der Datenbank kommen
+    { name: 'intelligence', value: 10, proficiency: false } //erstmal nur gemockt, später muss der Wert aus der Datenbank kommen
   );
   public intelligence$ = this.intelligenceSubject.asObservable();
 
   private wisdomSubject = new BehaviorSubject<Attribute>(
-    { name: 'wisdom', value: 13, proficiency: true } //erstmal nur gemockt, später muss der Wert aus der Datenbank kommen
+    { name: 'wisdom', value: 10, proficiency: true } //erstmal nur gemockt, später muss der Wert aus der Datenbank kommen
   );
   public wisdom$ = this.wisdomSubject.asObservable();
 
   private charismaSubject = new BehaviorSubject<Attribute>(
-    { name: 'charisma', value: 19, proficiency: false } //erstmal nur gemockt, später muss der Wert aus der Datenbank kommen
+    { name: 'charisma', value: 10, proficiency: false } //erstmal nur gemockt, später muss der Wert aus der Datenbank kommen
   );
   public charisma$ = this.charismaSubject.asObservable();
 
   // #endregion
 
-  // #region Skill observables
+  // #region # SKILLS
 
-  /**
-   *
-   * @param newValue The new value of the skill
-   */
   updateSkill(newValue: Skill) {
     const functionCall: string = newValue.name + 'Subject.next(newValue)';
     eval(`this.${functionCall}`);
-    console.log('updateSkill: ', newValue.name);
-    // TODO: Datenbank updaten
+    const skillsData = {
+      acrobatics: this.acrobaticsSubject.getValue(),
+      animalHandling: this.animalHandlingSubject.getValue(),
+      arcana: this.arcanaSubject.getValue(),
+      athletics: this.athleticsSubject.getValue(),
+      deception: this.deceptionSubject.getValue(),
+      history: this.historySubject.getValue(),
+      insight: this.insightSubject.getValue(),
+      intimidation: this.intimidationSubject.getValue(),
+      investigation: this.investigationSubject.getValue(),
+      medicine: this.medicineSubject.getValue(),
+      nature: this.natureSubject.getValue(),
+      perception: this.perceptionSubject.getValue(),
+      performance: this.performanceSubject.getValue(),
+      persuasion: this.persuasionSubject.getValue(),
+      religion: this.religionSubject.getValue(),
+      sleightOfHand: this.sleightOfHandSubject.getValue(),
+      stealth: this.stealthSubject.getValue(),
+      survival: this.survivalSubject.getValue(),
+    };
+    this.setData('skills', skillsData);
   }
 
   private acrobaticsSubject = new BehaviorSubject<Skill>(
@@ -943,87 +660,39 @@ export class DataService {
 
   // #endregion
 
-  // #region inventory
-
-  private _items: SimpleItem[] = [
-    {
-      name: 'Kurzbogen +1',
-      weight: 5,
-      value: 50,
-      quantity: 1,
-      description: 'Hallo',
-    },
-    {
-      name: 'Helebade',
-      weight: 10,
-      value: 5,
-      quantity: 1,
-      description: 'Hallo',
-    },
-    {
-      name: 'Armbrust',
-      weight: 8,
-      value: 6,
-      quantity: 1,
-      description: 'Hallo',
-    },
-  ];
-
-  public get items(): SimpleItem[] {
-    return this._items;
-  }
-
-  public set items(newValue: SimpleItem[]) {
-    console.log('items: ', newValue);
-    this._items = newValue;
-  }
-
-  private _food: Food[] = [
-    {
-      name: 'Brot',
-      quantity: 7,
-      isReady: true,
-      weight: 0.5,
-    },
-    {
-      name: 'Suppe',
-      quantity: 4,
-      isReady: true,
-      weight: 0.5,
-    },
-    {
-      name: 'Fleisch',
-      quantity: 2,
-      isReady: false,
-      weight: 0.5,
-    },
-    {
-      name: 'Käse',
-      quantity: 1,
-      isReady: false,
-      weight: 0.5,
-    },
-    {
-      name: 'Bier',
-      quantity: 1,
-      isReady: false,
-      weight: 0.5,
-    },
-    {
-      name: 'Wein',
-      quantity: 1,
-      isReady: false,
-      weight: 0.5,
-    },
-  ];
+  // #region # inventory
+
+  private _favoriteWeapons: Weapon[] = [];
+
+  public get favoriteWeapons(): Weapon[] {
+    return this._favoriteWeapons;
+  }
+
+  public set favoriteWeapons(weapons: Weapon[]) {
+    this._favoriteWeapons = weapons;
+    this.setData('favoriteWeapons', { data: this._favoriteWeapons });
+  }
+
+  private _weaponsAndArmor: SimpleItem[] = [];
+
+  public get weaponsAndArmor(): SimpleItem[] {
+    return this._weaponsAndArmor;
+  }
+
+  public set weaponsAndArmor(newValue: SimpleItem[]) {
+    this._weaponsAndArmor = newValue;
+    this.setData('weaponsAndArmor', { data: this._weaponsAndArmor });
+  }
+
+  private _food: Food[] = [];
 
   public get food(): Food[] {
     return this._food;
   }
 
   public set food(newValue: Food[]) {
-    // console.log('food: ', newValue);
     this._food = newValue;
+    this.setData('food', { data: this._food });
   }
 
   private _money: any = {
@@ -1039,60 +708,30 @@ export class DataService {
   }
 
   public set money(newValue: any) {
-    console.log('money: ', newValue);
     this._money = newValue;
+    this.setData('money', this._money);
   }
 
-  private _consumbales: SimpleItem[] = [
-    {
-      name: 'Heiltrank',
-      weight: 0.5,
-      value: 50,
-      quantity: 1,
-      description: 'Heilt 1W8 + 2 Trefferpunkte',
-    },
-    {
-      name: 'Öl',
-      weight: 1,
-      value: 1,
-      quantity: 1,
-      description: 'Brennt',
-    },
-  ];
+  private _consumbales: SimpleItem[] = [];
 
   public get consumables(): SimpleItem[] {
     return this._consumbales;
   }
 
   public set consumables(newValue: SimpleItem[]) {
-    console.log('consumables: ', newValue);
     this._consumbales = newValue;
+    this.setData('consumables', { data: this._consumbales });
   }
 
-  private _miscellaneous: SimpleItem[] = [
-    {
-      name: 'Rucksack',
-      weight: 5,
-      value: 2,
-      quantity: 1,
-      description: 'Hallo',
-    },
-    {
-      name: 'Seil',
-      weight: 5,
-      value: 2,
-      quantity: 1,
-      description: 'Hallo',
-    },
-  ];
+  private _miscellaneous: SimpleItem[] = [];
 
   public get miscellaneous(): SimpleItem[] {
     return this._miscellaneous;
   }
 
   public set miscellaneous(newValue: SimpleItem[]) {
-    console.log('misc: ', newValue);
     this._miscellaneous = newValue;
+    this.setData('miscellaneous', { data: this._miscellaneous });
   }
 
   // #endregion
@@ -1129,125 +768,17 @@ export class DataService {
     return this.db.collection(collection).get({ keys: true });
   }
 
-  public deleteCollection(collection: string) {
-    this.db.collection(collection).delete();
+  public setData(key: string, data: any) {
+    return this.db
+      .collection(this.characterName)
+      .doc(key)
+      .set(data)
+      .then(() => {});
   }
 
-  // #endregion
-
-  // #region character selection and construction
-
-  /**
-   *
-   * @param character The name of the character that was selected
-   */
-  public async selectCharacter(character: string) {
-    console.log('selectCharacter ausgewählt');
-    const characterData = await this.getCollectionWithKeys(character);
-    this.character = this.buildCurrentCharacter(characterData);
-    console.log('character: ', this.character);
-    this.Router.navigate(['journal']);
+  public deleteCollection(collection: string) {
+    this.db.collection(collection).delete();
   }
 
-  private buildCurrentCharacter(currentCharacterData: any): Character {
-    const [
-      appearanceData,
-      attributesData,
-      characterData,
-      combatStatsData,
-      personalityData,
-      skillsData,
-      weaponsData,
-    ] = currentCharacterData.map((entry: any) => entry.data);
-
-    const appearance = {
-      age: appearanceData.age,
-      height: appearanceData.height,
-      weight: appearanceData.weight,
-      eyes: appearanceData.eyes,
-      skin: appearanceData.skin,
-      hair: appearanceData.hair,
-    };
-
-    const attributes = {
-      strength: attributesData.strength,
-      dexterity: attributesData.dexterity,
-      constitution: attributesData.constitution,
-      intelligence: attributesData.intelligence,
-      wisdom: attributesData.wisdom,
-      charisma: attributesData.charisma,
-    };
-
-    const character = {
-      name: characterData.name,
-      race: characterData.race,
-      class: characterData.class,
-      subclass: characterData.subclass,
-      level: characterData.level,
-      background: characterData.background,
-      experience: characterData.experience,
-      inspiration: characterData.inspiration,
-      proficiencyBonus: characterData.proficiencyBonus,
-    };
-
-    const combatStats = {
-      armorClass: combatStatsData.armorClass,
-      initiative: combatStatsData.initiative,
-      speed: combatStatsData.speed,
-      hitPointMaximum: combatStatsData.hitPointMaximum,
-      currentHitPoints: combatStatsData.currentHitPoints,
-      temporaryHitPoints: combatStatsData.temporaryHitPoints,
-      hitDice: combatStatsData.hitDice,
-      deathSaveSuccesses: combatStatsData.deathSaveSuccesses,
-      deathSaveFailures: combatStatsData.deathSaveFailures,
-    };
-
-    const personality = {
-      personalityTraits: personalityData.personalityTraits,
-      ideals: personalityData.ideals,
-      bonds: personalityData.bonds,
-      flaws: personalityData.flaws,
-    };
-
-    const skills = {
-      acrobatics: skillsData.acrobatics,
-      animalHandling: skillsData.animalHandling,
-      arcana: skillsData.arcana,
-      athletics: skillsData.athletics,
-      deception: skillsData.deception,
-      history: skillsData.history,
-      insight: skillsData.insight,
-      intimidation: skillsData.intimidation,
-      investigation: skillsData.investigation,
-      medicine: skillsData.medicine,
-      nature: skillsData.nature,
-      perception: skillsData.perception,
-      performance: skillsData.performance,
-      persuasion: skillsData.persuasion,
-      religion: skillsData.religion,
-      sleightOfHand: skillsData.sleightOfHand,
-      stealth: skillsData.stealth,
-      survival: skillsData.survival,
-    };
-    const weapons = {
-      name: weaponsData.name,
-      attackBonus: weaponsData.attackBonus,
-      damage: weaponsData.damage,
-      damageType: weaponsData.damageType,
-      range: weaponsData.range,
-      description: weaponsData.damageDice,
-    };
-    const newCharacter: Character = {
-      appearance: appearance,
-      attributes: attributes,
-      characterData: character,
-      combatStats: combatStats,
-      personality: personality,
-      skills: skills,
-      weapons: weapons,
-    };
-    console.log('newCharacter: ', newCharacter);
-    return newCharacter;
-  }
   // #endregion
 }

+ 16 - 0
src/services/dataResolve/data-resolver.service.spec.ts

@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { DataResolverService } from './data-resolver.service';
+
+describe('DataResolverService', () => {
+  let service: DataResolverService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(DataResolverService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});

+ 14 - 0
src/services/dataResolve/data-resolver.service.ts

@@ -0,0 +1,14 @@
+import { Injectable } from '@angular/core';
+import { DataService } from '../data/data.service';
+import { Resolve } from '@angular/router';
+
+@Injectable({
+  providedIn: 'root',
+})
+export class DataResolverService implements Resolve<any> {
+  constructor(private dataAccessor: DataService) {}
+
+  resolve(): Promise<any> {
+    return this.dataAccessor.loadData();
+  }
+}

+ 0 - 21
src/services/database/database.service.ts

@@ -1,21 +0,0 @@
-import { Injectable } from '@angular/core';
-import { BehaviorSubject } from 'rxjs';
-
-@Injectable({
-  providedIn: 'root',
-})
-export class DatabaseService {
-  private dataSubject = new BehaviorSubject<string>('Initial Value');
-  public data$ = this.dataSubject.asObservable();
-
-  updateData(newValue: string) {
-    this.dataSubject.next(newValue);
-  }
-
-  ngOnInit(): void {
-    setTimeout(() => {
-      this.updateData('New Value');
-      console.log('updated data');
-    }, 5000);
-  }
-}

+ 0 - 1
src/services/details/details.service.ts

@@ -20,7 +20,6 @@ export class DetailsService {
 
   // Is called from the dynamic component to close the offcanvas
   public closePanel(result: any, data?: any) {
-    console.log('closePanel', result);
     // Is listened to in the host component where the panel was opened, to initiate further steps
     this.resultSubject.next({ state: result, data: data });
     this.closePanelSubject.next('close');

+ 4 - 4
src/services/database/database.service.spec.ts → src/services/spells/spells.service.spec.ts

@@ -1,13 +1,13 @@
 import { TestBed } from '@angular/core/testing';
 
-import { DatabaseService } from './database.service';
+import { SpellsService } from './spells.service';
 
-describe('DatabaseService', () => {
-  let service: DatabaseService;
+describe('SpellsService', () => {
+  let service: SpellsService;
 
   beforeEach(() => {
     TestBed.configureTestingModule({});
-    service = TestBed.inject(DatabaseService);
+    service = TestBed.inject(SpellsService);
   });
 
   it('should be created', () => {

+ 395 - 0
src/services/spells/spells.service.ts

@@ -0,0 +1,395 @@
+import { Injectable } from '@angular/core';
+import { Spell } from 'src/interfaces/spell';
+import { Subject } from 'rxjs';
+
+@Injectable({
+  providedIn: 'root',
+})
+export class SpellsService {
+  // Functions
+
+  private _closeSubject = new Subject<number>();
+
+  public closeSubject$ = this._closeSubject.asObservable();
+
+  public closeAllOthers(level: number): void {
+    this._closeSubject.next(level);
+  }
+
+  // Spells
+  private level0: string[] = [
+    'Führung (Guidance)',
+    'Totenläuten (Toll the Dead)',
+    'Thaumaturgie (Thaumaturgy)',
+  ];
+
+  private level1: string[] = [
+    'Wunden Heilen (Cure Wounds)',
+    'Magie entdecken (Detect Magic)',
+    'Gift und Krankheiten entdecken (Detect Poison and Disease)',
+    'Segnen (Bless)',
+    'Heilendes Wort (Healing Word)',
+    'Lenkendes Geschoss (Guiding Bolt)',
+    'Heldenmut (Heroism)',
+    'Heiligtum (Sanctuary)',
+  ];
+
+  private level2: string[] = [];
+
+  private level3: string[] = [];
+
+  private level4: string[] = [];
+
+  private level5: string[] = [];
+
+  private level6: string[] = [];
+
+  private level7: string[] = [];
+
+  private level8: string[] = [];
+
+  private level9: string[] = [];
+
+  public getListOfSpells(level: number) {
+    switch (level) {
+      case 0:
+        return this.level0;
+      case 1:
+        return this.level1;
+      case 2:
+        return this.level2;
+      case 3:
+        return this.level3;
+      case 4:
+        return this.level4;
+      case 5:
+        return this.level5;
+      case 6:
+        return this.level6;
+      case 7:
+        return this.level7;
+      case 8:
+        return this.level8;
+      case 9:
+        return this.level9;
+      default:
+        return this.level0;
+    }
+  }
+
+  public getSpell(name: string): Spell {
+    switch (name) {
+      case 'Führung (Guidance)':
+        return this.guidance;
+      case 'Totenläuten (Toll the Dead)':
+        return this.tollTheDead;
+      case 'Thaumaturgie (Thaumaturgy)':
+        return this.thaumaturgie;
+      case 'Wunden Heilen (Cure Wounds)':
+        return this.cureWounds;
+      case 'Magie entdecken (Detect Magic)':
+        return this.detectMagic;
+      case 'Segnen (Bless)':
+        return this.bless;
+      case 'Heilendes Wort (Healing Word)':
+        return this.healingWord;
+      case 'Lenkendes Geschoss (Guiding Bolt)':
+        return this.guidingBolt;
+      case 'Gift und Krankheiten entdecken (Detect Poison and Disease)':
+        return this.detectPoisonAndDisease;
+      case 'Heldenmut (Heroism)':
+        return this.heroism;
+      case 'Heiligtum (Sanctuary)':
+        return this.santcuary;
+      default:
+        return this.guidance;
+    }
+  }
+
+  private guidance: Spell = {
+    name: 'Göttliche Führung',
+    level: 0,
+    cost: 'action',
+    duration: 10,
+    canRitual: false,
+    needsVerbal: true,
+    needsSomatic: true,
+    needsMaterial: false,
+    needsConcentration: true,
+    needsAttackRoll: false,
+    needsSavingThrow: false,
+    doesDamage: false,
+    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+    doesHeal: false,
+    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+    description:
+      'Du berührst eine bereitwillige Kreatur. Einmal vor dem Ende des Zaubers kann das Ziel mit einem W4 würfeln und das Ergebnis zu einem Attributswurf seiner Wahl addieren. Es kann mit dem W4 vor oder nach dem Attributswurf würfeln. Dann endet der Zauber.',
+    school: 'divination',
+    isRanged: false,
+    range: 5,
+    hasAreaOfEffect: false,
+    areaOfEffectType: '',
+    radius: 0,
+  };
+
+  private tollTheDead: Spell = {
+    name: 'Totenläuten',
+    level: 0,
+    cost: 'action',
+    duration: 0,
+    canRitual: false,
+    needsVerbal: true,
+    needsSomatic: true,
+    needsMaterial: false,
+    needsConcentration: false,
+    needsAttackRoll: false,
+    needsSavingThrow: true,
+    savingThrowAttribute: 'wisdom',
+    doesDamage: true,
+    damage: [{ diceNumber: '1', diceType: 'd8', damageType: 'necrotic' }],
+    doesHeal: false,
+    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+    description:
+      'Du zeigst auf eine Kreatur innehalb von 18 Meter, die du sehen kannst und der Klang schmerzhafter Glocken füllt die Luft um sie herum für einen Moment. Das Ziel muss einen Weisheitsrettungswurf machen oder 1W8 Nekrotischen Schaden erleiden. Wenn das Ziel bereits Schaden erlitten hat, erhöht sich der Schaden auf 1W12.',
+    school: 'necromancy',
+    isRanged: true,
+    range: 60,
+    hasAreaOfEffect: false,
+    areaOfEffectType: '',
+    radius: 0,
+  };
+
+  private thaumaturgie: Spell = {
+    name: 'Thaumaturgie',
+    level: 0,
+    cost: 'action',
+    duration: 10,
+    canRitual: false,
+    needsVerbal: true,
+    needsSomatic: false,
+    needsMaterial: false,
+    needsConcentration: false,
+    needsAttackRoll: false,
+    needsSavingThrow: false,
+    doesDamage: false,
+    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+    doesHeal: false,
+    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+    description:
+      'Du erzeugst einen der folgenden magischen Effekte innerhalb von 9 Metern: \b\b • Deine Stimme wird dreimal lauter.\b • Du lässt einen unverschlossenen Tür oder ein Fenster aufspringen. \b• Du lässt eine Flamme aufleuchten, erlöschen oder ihre Farbe ändern. \b• Du lässt den Boden für eine Minute beben.\b • Du erzeugst Geräusche, die von dir ausgehen, wie das Flüstern einer Stimme, das Brüllen eines Löwen, das Knurren von Donner oder das Klappern von Metall. \b• Du lässt eine Tür oder ein Fenster, das du berührst, für eine Minute verriegeln oder entriegeln.',
+    school: 'transmutation',
+    isRanged: true,
+    range: 30,
+    hasAreaOfEffect: false,
+    areaOfEffectType: '',
+    radius: 0,
+  };
+
+  private cureWounds: Spell = {
+    name: 'Heilende Berührung',
+    level: 1,
+    cost: 'action',
+    duration: 0,
+    canRitual: false,
+    needsVerbal: true,
+    needsSomatic: true,
+    needsMaterial: false,
+    needsConcentration: false,
+    needsAttackRoll: false,
+    needsSavingThrow: false,
+    doesDamage: false,
+    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+    doesHeal: true,
+    heal: { diceNumber: '1', diceType: 'd8', additionalHeal: 4 },
+    description:
+      'Eine Kreatur, die du berührst, gewinnt Trefferpunkte in Höhe von 1W8 + deinem Attributsmodifikator im Zauberwirken zurück. Dieser Zauber wirkt nicht auf Untote oder Konstrukte. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, steigt die Heilung für jeden Grad über dem 1. um 1W8.',
+    school: 'evocation',
+    isRanged: false,
+    range: 5,
+    hasAreaOfEffect: false,
+    areaOfEffectType: '',
+    radius: 0,
+  };
+
+  private detectMagic: Spell = {
+    name: 'Magie entdecken',
+    level: 1,
+    cost: 'action',
+    duration: 100,
+    canRitual: true,
+    needsVerbal: true,
+    needsSomatic: true,
+    needsMaterial: false,
+    needsConcentration: false,
+    needsAttackRoll: false,
+    needsSavingThrow: false,
+    doesDamage: false,
+    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+    doesHeal: false,
+    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+    description:
+      'Während der Wirkungsdauer nimmst du die Gegenwart von Magie im Abstand von bis zu neun Metern von dir wahr. Wenn du auf diese Weise Magie wahrnimmst, kannst du deine Aktion verwenden, um schwache Auren um sichtbare magische Kreaturen oder Objekte sowie ihre magische Schule zu erkennen, falls vorhanden. Dieser Zauber durchdringt die meisten Barrieren, wird aber von 30 Zentimetern Stein, 2,5 Zentimetern gewöhnlichem Metall, dünnem Bleiblech sowie von einem Meter Holz oder Erde blockiert.',
+    school: 'divination',
+    isRanged: true,
+    range: 30,
+    hasAreaOfEffect: false,
+    areaOfEffectType: '',
+    radius: 0,
+  };
+
+  private bless: Spell = {
+    name: 'Segnen',
+    level: 1,
+    cost: 'action',
+    duration: 10,
+    canRitual: false,
+    needsVerbal: true,
+    needsSomatic: true,
+    needsMaterial: true,
+    needsConcentration: true,
+    needsAttackRoll: false,
+    needsSavingThrow: false,
+    doesDamage: false,
+    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+    doesHeal: false,
+    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+    description:
+      'Du segnest bis zu drei Kreaturen deiner Wahl in Reichweite. Wenn ein Ziel während der Wirkungsdauer einen Angriffs- oder Rettungswurf ausführt, darf es mit einem W4 würfeln und das Würfelergebnis zu seinem Angriffs- oder Rettungswurf addieren. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, kannst du für jeden Grad über dem 1. eine zusätzliche Kreatur als Ziel wählen.',
+    school: 'enchantment',
+    isRanged: true,
+    range: 60,
+    hasAreaOfEffect: false,
+    areaOfEffectType: '',
+    radius: 0,
+  };
+
+  private healingWord: Spell = {
+    name: 'Heilendes Wort',
+    level: 1,
+    cost: 'bonus action',
+    duration: 0,
+    canRitual: false,
+    needsVerbal: true,
+    needsSomatic: false,
+    needsMaterial: false,
+    needsConcentration: false,
+    needsAttackRoll: false,
+    needsSavingThrow: false,
+    doesDamage: false,
+    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+    doesHeal: true,
+    heal: { diceNumber: '1', diceType: 'd4', additionalHeal: 4 },
+    description:
+      'Eine Kreatur deiner Wahl in Reichweite, die du sehen kannst, gewinnt Trefferpunkte in Höhe von 1W4 + deinem Zauberwirken-Attributsmodifikator zurück. Dieser Zauber wirkt nicht auf Untote oder Konstrukte. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, steigt die Heilung für jeden Grad über dem 1. um 1W4.',
+    school: 'evocation',
+    isRanged: true,
+    range: 60,
+    hasAreaOfEffect: false,
+    areaOfEffectType: '',
+    radius: 0,
+  };
+
+  private guidingBolt: Spell = {
+    name: 'Lenkendes Geschoss',
+    level: 1,
+    cost: 'action',
+    duration: 0,
+    canRitual: false,
+    needsVerbal: true,
+    needsSomatic: true,
+    needsMaterial: false,
+    needsConcentration: false,
+    needsAttackRoll: true,
+    needsSavingThrow: false,
+    doesDamage: true,
+    damage: [{ diceNumber: '4', diceType: 'd6', damageType: 'radiant' }],
+    doesHeal: false,
+    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+    description:
+      'Ein Lichtblitz schießt auf eine Kreatur deiner Wahl in Reichweite zu. Führe einen Fernkampf-Zauberangriff gegen das Ziel aus. Bei einem Erfolg erleidet das Ziel 4W6 gleißenden Schaden. Dank des mystischen dämmrigen Lichts, das auf dem Ziel glitzert, ist der nächste Angriffswurf gegen das Ziel vor Ende deines nächsten Zugs im Vorteil. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, steigt der Schaden für jeden Grad über dem 1. um 1W6.',
+    school: 'evocation',
+    isRanged: true,
+    range: 120,
+    hasAreaOfEffect: false,
+    areaOfEffectType: '',
+    radius: 0,
+  };
+
+  private detectPoisonAndDisease: Spell = {
+    name: 'Gift und Krankheiten entdecken',
+    level: 1,
+    cost: 'action',
+    duration: 100,
+    canRitual: true,
+    needsVerbal: true,
+    needsSomatic: true,
+    needsMaterial: true,
+    needsConcentration: true,
+    needsAttackRoll: false,
+    needsSavingThrow: false,
+    doesDamage: false,
+    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+    doesHeal: false,
+    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+    description:
+      'Während der Wirkungsdauer nimmst du die Gegenwart und die Position von Giften, giftigen Kreaturen und Krankheiten im Abstand von bis zu neun Metern von dir wahr. Du kannst auch die Art des Gifts, der giftigen Kreatur oder der Krankheit bestimmen. Dieser Zauber durchdringt die meisten Barrieren, wird aber von 30 Zentimetern Stein, 2,5 Zentimetern gewöhnlichem Metall, dünnem Bleiblech sowie von einem Meter Holz oder Erde blockiert.',
+    school: 'divination',
+    isRanged: true,
+    range: 30,
+    hasAreaOfEffect: false,
+    areaOfEffectType: '',
+    radius: 0,
+  };
+
+  private heroism: Spell = {
+    name: 'Heldenmut',
+    level: 1,
+    cost: 'action',
+    duration: 10,
+    canRitual: false,
+    needsVerbal: true,
+    needsSomatic: true,
+    needsMaterial: false,
+    needsConcentration: true,
+    needsAttackRoll: false,
+    needsSavingThrow: false,
+    doesDamage: false,
+    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+    doesHeal: false,
+    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+    description:
+      'Du berührst eine bereitwillige Kreatur und erfüllst sie mit Tapferkeit. Bis der Zauber endet, kann die Kreatur nicht verängstigt werden, und sie erhält zu Beginn jedes ihrer Züge temporäre Trefferpunkte in Höhe deines Zauberwirken-Attributsmodifikators. Wenn der Zauber endet, verliert das Ziel alle verbleibenden temporären Trefferpunkte dieses Zaubers. Auf höheren Graden: Wirkst du diesen Zauber, indem du einen Zauberplatz des 2. Grades oder höher nutzt, kannst du für jeden Grad über dem 1. eine zusätzliche Kreatur als Ziel wählen.',
+    school: 'enchantment',
+    isRanged: false,
+    range: 5,
+    hasAreaOfEffect: false,
+    areaOfEffectType: '',
+    radius: 0,
+  };
+
+  private santcuary: Spell = {
+    name: 'Heiligtum',
+    level: 1,
+    cost: 'bonus action',
+    duration: 10,
+    canRitual: false,
+    needsVerbal: true,
+    needsSomatic: true,
+    needsMaterial: true,
+    needsConcentration: true,
+    needsAttackRoll: false,
+    needsSavingThrow: false,
+    doesDamage: false,
+    damage: [{ diceNumber: '', diceType: '', damageType: '' }],
+    doesHeal: false,
+    heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
+    description:
+      'Du schützt eine Kreatur in Reichweite vor Angriffen. Während der Wirkungsdauer muss jede Kreatur, die bei einem Angriff oder schädlichen Zauber die geschützte Kreatur als Ziel hat, zuerst einen Weisheitsrettungswurf bestehen. Scheitert der Wurf, muss die Kreatur ein neues Ziel wählen, oder sie verliert den Angriff oder Zauber. Dieser Zauber schützt die entsprechende Kreatur nicht vor Flächeneffekten wie der Explosion eines Feuerballs. Greift die geschützte Kreatur an oder wirkt sie einen Zauber auf eine feindliche Kreatur, endet der Effekt dieses Zaubers.',
+    school: 'abjuration',
+    isRanged: true,
+    range: 60,
+    hasAreaOfEffect: false,
+    areaOfEffectType: '',
+    radius: 0,
+  };
+}

+ 89 - 105
src/styles.scss

@@ -1,17 +1,10 @@
 /* You can add global styles to this file, and also import other style files */
 
 /* Importing Bootstrap SCSS file. */
-@import 'bootstrap/scss/bootstrap';
-
-$dialog-position-top: 10%;
-$dialog-position-left: 20%;
-$dialog-position-right: 20%;
-
-@import 'node_modules/ngx-smart-modal/styles/ngx-smart-modal.scss';
-
-
-:root{
+// TODO: remove bootstrap styles
+@import "bootstrap/scss/bootstrap";
 
+:root {
     // COLORS
     --primary-color: #d8ac96;
     --primary-color-light: #e8c7b5;
@@ -31,27 +24,36 @@ $dialog-position-right: 20%;
     --field-background-color: #efc8af;
 
     --border-color: #8d8c8c;
-    --border-color2: #8d8c8c;
 
+    // OFFICIAL COLORS
+    --accept: #84a36f;
+    --accept-hover: #6f9158;
+    --delete: #a45a52;
+    --delete-hover: #8f4a42;
+    --edit: #86a5b7;
+    --edit-hover: #6f8e9f;
+
+    --modal-background: antiquewhite;
 
     // shadows
-    --shadow-small: 4px 4px 10px 4px rgba(0,0,0,0.2);
-    --shadow-medium: 4px 4px 10px 6px rgba(0,0,0,0.2);
-    --shadow-large: 4px 4px 10px 8px rgba(0,0,0,0.2);                   
-    
-    @mixin field-styling{
+    --shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.5);
+    --shadow-small: 4px 4px 10px 4px rgba(0, 0, 0, 0.2);
+    --shadow-medium: 4px 4px 10px 6px rgba(0, 0, 0, 0.2);
+    --shadow-large: 4px 4px 10px 8px rgba(0, 0, 0, 0.2);
+
+    @mixin field-styling {
         border: solid 1px var(--border-color);
         background-color: var(--field-background-color);
         box-shadow: var(--shadow-small);
         border-radius: 10px;
     }
 
-    // 
-    .responsive-small{
+    //
+    .responsive-small {
         display: none;
     }
 
-    .responsive-large{
+    .responsive-large {
         display: block;
     }
 
@@ -69,7 +71,6 @@ $dialog-position-right: 20%;
         width: 0.5rem;
         margin: 0.125rem 0;
         height: 0.5rem;
-
     }
 
     ::-webkit-scrollbar-track {
@@ -88,11 +89,9 @@ $dialog-position-right: 20%;
     }
 }
 
-
-
 //  Info Row
 
-.info-container{
+.info-container {
     display: flex;
     flex-direction: column;
     justify-content: space-between;
@@ -102,15 +101,15 @@ $dialog-position-right: 20%;
     box-shadow: var(--shadow-small);
     border-radius: 10px;
     height: 7rem;
-    @media (width > 1699px){
-       width: 10rem;     
+    @media (width > 1699px) {
+        width: 10rem;
     }
     @media (width < 1640px) {
-        width: 7rem
+        width: 7rem;
     }
 }
 
-.info-input{
+.info-input {
     border: none;
     outline: none;
     text-align: center;
@@ -127,7 +126,7 @@ $dialog-position-right: 20%;
     margin: 0;
 }
 
-.info-label{
+.info-label {
     font-size: 1.25rem;
     font-weight: 600;
     text-align: center;
@@ -139,19 +138,18 @@ input[type="checkbox"] {
     width: 1.25rem;
 }
 
-
 // Value Box
 
-.value-row{
+.value-row {
     display: flex;
     justify-content: space-around;
 }
 
-.value-container{
+.value-container {
     width: 6rem;
 }
 
-.value-box{
+.value-box {
     width: 3.5rem;
     height: 3rem;
     font-size: 1.5rem;
@@ -164,7 +162,7 @@ input[type="checkbox"] {
     box-shadow: var(--shadow-small);
 }
 
-.value-input{
+.value-input {
     border: none;
     outline: none;
     text-align: center;
@@ -175,23 +173,19 @@ input[type="checkbox"] {
         -webkit-appearance: none;
         appearance: none;
         margin: 0;
-    }   
+    }
 }
 
-.value-label{
+.value-label {
     font-size: 1.25rem;
     font-weight: 600;
     text-align: center;
     padding: 0.5rem;
 }
 
-
-
-
-
 // details-panel
 
-.vertical-button-wrapper-3{
+.vertical-button-wrapper-3 {
     width: 100%;
     position: absolute;
     bottom: 2rem;
@@ -204,7 +198,7 @@ input[type="checkbox"] {
     justify-content: center;
 }
 
-.vertical-button-wrapper-2{
+.vertical-button-wrapper-2 {
     width: 100%;
     position: absolute;
     bottom: 2rem;
@@ -217,7 +211,7 @@ input[type="checkbox"] {
     justify-content: center;
 }
 
-.button-wrapper-3-block{
+.button-wrapper-3-block {
     display: grid;
     grid-template-rows: 1fr 1fr 1fr;
     grid-template-columns: 1fr;
@@ -228,7 +222,7 @@ input[type="checkbox"] {
     padding-bottom: 2rem;
 }
 
-.button-wrapper-2-block{
+.button-wrapper-2-block {
     display: grid;
     grid-template-rows: 1fr 1fr;
     grid-template-columns: 1fr;
@@ -241,216 +235,206 @@ input[type="checkbox"] {
 
 //
 
-.details-title{
+.details-title {
     text-align: center;
     font-size: 2rem;
     font-weight: bold;
     margin-top: 1.5rem;
 }
 
-.details-heading{
+.details-heading {
     font-size: 1.5rem;
     font-weight: bold;
     margin-top: 1.5rem;
 }
 
-.details-subheading{
+.details-subheading {
     font-size: 1.25rem;
     font-weight: bold;
     margin-top: 1.5rem;
 }
 
-
-.details-content{
+.details-content {
     margin-top: 1.5rem;
 }
 
-.details-content-small{
+.details-content-small {
     margin-top: 0.5rem;
 }
 
-.details-bold{
+.details-bold {
     font-weight: 500;
 }
 
-.centered{
+.centered {
     text-align: center;
 }
 
-.details-value-container{
+.details-value-container {
     margin-top: 2rem;
 }
 
-
-.details-flex-row{
+.details-flex-row {
     display: flex;
     justify-content: space-between;
     align-items: center;
 }
 
-.top-1{
+.top-1 {
     margin-top: 1rem;
 }
 
-.top-2{
+.top-2 {
     margin-top: 2rem;
 }
 
-.top-3{
+.top-3 {
     margin-top: 3rem;
 }
 
-.details-value{
+.details-value {
     width: 3.5rem;
     height: 3rem;
     font-size: 1.5rem;
     display: flex;
     justify-content: center;
     align-items: center;
-    margin:auto;
+    margin: auto;
     border-radius: 10px;
     background-color: white;
     box-shadow: var(--shadow-small);
 }
 
-.details-label{
+.details-label {
     margin-top: 0.5rem;
     font-weight: 500;
     text-align: center;
 }
 
-.details-name{
+.details-name {
     font-size: 1.5rem;
     font-weight: bold;
     margin-top: 1.5rem;
-    text-align:center;
+    text-align: center;
 }
 
-.details-long-description{
+.details-long-description {
     margin: 2rem 1rem;
     font-size: 1rem;
 }
 
+//
 
-
-
-
-
-
-
-
-
-
-
-
-// 
-
-
-.flex-row{
+.flex-row {
     display: flex;
     flex-direction: row;
 }
 
-.flex-column{
+.flex-column {
     display: flex;
     flex-direction: column;
 }
 
-.flex-centered{
+.flex-centered {
     align-items: center;
     justify-content: center;
 }
 
-.flex-left{
+.flex-left {
     justify-content: start;
 }
 
-.flex-right{
+.flex-right {
     justify-content: end;
 }
 
-.gap-01{
+.gap-01 {
     gap: 0.1rem;
 }
 
-.gap-02{
+.gap-02 {
     gap: 0.2rem;
 }
 
-.gap-03{
+.gap-03 {
     gap: 0.3rem;
 }
 
-.gap-04{
+.gap-04 {
     gap: 0.4rem;
 }
 
-.gap-05{
+.gap-05 {
     gap: 0.5rem;
 }
 
-.gap-06{
+.gap-06 {
     gap: 0.6rem;
 }
 
-.gap-07{
+.gap-07 {
     gap: 0.7rem;
 }
 
-.gap-08{
+.gap-08 {
     gap: 0.8rem;
 }
 
-.gap-09{
+.gap-09 {
     gap: 0.9rem;
 }
 
-.gap-10{
+.gap-10 {
     gap: 1rem;
 }
 
-.gap-11{
+.gap-11 {
     gap: 1.1rem;
 }
 
-.gap-12{
+.gap-12 {
     gap: 1.2rem;
 }
 
-.gap-13{
+.gap-13 {
     gap: 1.3rem;
 }
 
-.gap-14{
+.gap-14 {
     gap: 1.4rem;
 }
 
-.gap-15{
+.gap-15 {
     gap: 1.5rem;
 }
 
-.gap-16{
+.gap-16 {
     gap: 1.6rem;
 }
 
-.gap-17{
+.gap-17 {
     gap: 1.7rem;
 }
 
-.gap-18{
+.gap-18 {
     gap: 1.8rem;
 }
 
-.gap-19{
+.gap-19 {
     gap: 1.9rem;
 }
 
-.gap-20{
+.gap-20 {
     gap: 2rem;
 }
 
 // Drag and Drop Table
 
-
-/* Importing Bootstrap SCSS file. */
-@import 'bootstrap/scss/bootstrap';
+html,
+body {
+    height: 100%;
+}
+body {
+    margin: 0;
+    // font-family: Roboto, "Helvetica Neue", sans-serif;
+}