Browse Source

Cleaned up and made everything stable

Warafear 1 year ago
parent
commit
f34204a7f8
55 changed files with 1765 additions and 846 deletions
  1. 144 0
      .nx/cache/d/daemon.log
  2. 1 1
      .nx/cache/d/server-process.json
  3. 2 0
      angular.json
  4. 822 15
      package-lock.json
  5. 2 2
      package.json
  6. 4 3
      src/app/app.module.ts
  7. 3 3
      src/app/character/character-creator/character-creator.component.ts
  8. 7 1
      src/app/character/character.module.ts
  9. 0 1
      src/app/journal/journal-home/journal-home.component.ts
  10. 8 4
      src/app/journal/journal-inventory/journal-inventory.component.ts
  11. 0 5
      src/app/journal/journal-inventory/simple-item-modal/simple-item-modal.component.ts
  12. 0 6
      src/app/journal/journal-spellcards/add-card/add-card.component.ts
  13. 1 3
      src/app/journal/journal-spellcards/journal-spellcards.component.ts
  14. 4 4
      src/app/journal/journal-stats/ability-panel/ability-panel.component.ts
  15. 73 71
      src/app/journal/journal-stats/ability-panel/ability-table/ability-modal/ability-modal.component.html
  16. 16 2
      src/app/journal/journal-stats/ability-panel/ability-table/ability-modal/ability-modal.component.scss
  17. 46 68
      src/app/journal/journal-stats/ability-panel/ability-table/ability-modal/ability-modal.component.ts
  18. 8 13
      src/app/journal/journal-stats/ability-panel/ability-table/ability-table.component.html
  19. 7 14
      src/app/journal/journal-stats/ability-panel/ability-table/ability-table.component.scss
  20. 44 36
      src/app/journal/journal-stats/ability-panel/ability-table/ability-table.component.ts
  21. 0 10
      src/app/journal/journal-stats/ability-panel/proficiencies-table/proficiencies-table.component.html
  22. 21 20
      src/app/journal/journal-stats/ability-panel/proficiencies-table/proficiencies-table.component.ts
  23. 20 7
      src/app/journal/journal-stats/ability-panel/proficiencies-table/tools-modal/tools-modal.component.html
  24. 14 5
      src/app/journal/journal-stats/ability-panel/proficiencies-table/tools-modal/tools-modal.component.scss
  25. 16 27
      src/app/journal/journal-stats/ability-panel/proficiencies-table/tools-modal/tools-modal.component.ts
  26. 63 50
      src/app/journal/journal-stats/ability-panel/spellslots/spellslots-modal/spellslots-modal.component.html
  27. 13 0
      src/app/journal/journal-stats/ability-panel/spellslots/spellslots-modal/spellslots-modal.component.scss
  28. 33 26
      src/app/journal/journal-stats/ability-panel/spellslots/spellslots-modal/spellslots-modal.component.ts
  29. 10 8
      src/app/journal/journal-stats/ability-panel/spellslots/spellslots.component.html
  30. 27 18
      src/app/journal/journal-stats/ability-panel/spellslots/spellslots.component.ts
  31. 54 51
      src/app/journal/journal-stats/ability-panel/trait-table/trait-modal/trait-modal.component.html
  32. 16 2
      src/app/journal/journal-stats/ability-panel/trait-table/trait-modal/trait-modal.component.scss
  33. 34 46
      src/app/journal/journal-stats/ability-panel/trait-table/trait-modal/trait-modal.component.ts
  34. 1 13
      src/app/journal/journal-stats/ability-panel/trait-table/trait-table.component.html
  35. 38 30
      src/app/journal/journal-stats/ability-panel/trait-table/trait-table.component.ts
  36. 0 1
      src/app/journal/journal-stats/attribute-skill-container/attribute-panel/attribute-field/attribute-field.component.ts
  37. 2 1
      src/app/journal/journal-stats/info-row/conditions/conditions.component.ts
  38. 2 5
      src/app/journal/journal-stats/life-container/life/life-details/life-details.component.ts
  39. 2 1
      src/app/journal/journal-stats/life-container/life/life.component.ts
  40. 5 3
      src/app/journal/journal-stats/weapons-container/spell-table/spell-table.component.ts
  41. 13 16
      src/app/journal/journal-stats/weapons-container/weapon-table/weapon-modal/weapon-modal.component.scss
  42. 0 1
      src/app/journal/journal-stats/weapons-container/weapon-table/weapon-modal/weapon-modal.component.ts
  43. 2 1
      src/app/journal/journal-stats/weapons-container/weapon-table/weapon-table.component.ts
  44. 0 2
      src/app/journal/journal-stats/weapons-container/weapons-container.component.ts
  45. 10 2
      src/app/journal/journal.module.ts
  46. 0 1
      src/app/journal/spell-modal/spell-modal.component.html
  47. 1 2
      src/app/journal/spell-modal/spell-modal.component.ts
  48. 11 6
      src/app/shared-components/full-spellcard/full-spellcard.component.html
  49. 19 11
      src/index.html
  50. 1 1
      src/interfaces/ability.ts
  51. 5 1
      src/interfaces/spell.ts
  52. 5 191
      src/services/data/data.service.ts
  53. 0 1
      src/services/details/details.service.ts
  54. 123 28
      src/services/spells/spells.service.ts
  55. 12 6
      src/styles.scss

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

@@ -461582,3 +461582,147 @@ To fix this, set a unique name for each project in a project.json inside the pro
     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":6796}
+{"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],

+ 3 - 3
src/app/character/character-creator/character-creator.component.ts

@@ -168,9 +168,9 @@ export class CharacterCreatorComponent {
       this.dataAccessor.addData(
         this.characterName,
         {
-          totalPoints: 4,
-          usedPoints: 2,
-          showKiPoints: true,
+          totalPoints: 0,
+          usedPoints: 0,
+          showKiPoints: false,
         },
         'kiPoints'
       ),

+ 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 {}

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

@@ -20,7 +20,6 @@ export class JournalHomeComponent {
   private isNavigationOpen: boolean = false;
 
   ngOnInit() {
-    console.log('JournalHomeComponent');
     const width = window.innerWidth;
     this.navigation.showNavigationPanel$.subscribe((state) => {
       this.isNavigationOpen = state;

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

@@ -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();
       }

+ 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();
     }

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

@@ -35,12 +35,6 @@ export class AddCardComponent {
 
   // Enlarged view
 
-  public showLargeSpellcard(reference: HTMLDivElement): void {
-    console.log(reference);
-
-    // reference.classList.add('large');
-  }
-
   public emitNewSpell(): void {
     this.createNewSpell.emit(this.level);
     this.resetThis();

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

@@ -81,6 +81,7 @@ export class JournalSpellcardsComponent {
         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);
@@ -88,7 +89,6 @@ export class JournalSpellcardsComponent {
         } else if (result.state === 'add') {
           this.dataAccessor.addFavoriteSpell(spell);
         } else if (result.state !== 'cancel') {
-          console.log(result.state);
           throw new Error('Unexpected result state, please send a bug report.');
         }
       }
@@ -225,14 +225,12 @@ export class JournalSpellcardsComponent {
   }
 
   public drop(event: CdkDragDrop<any[]>) {
-    // console.log(event);
     if (event.previousContainer === event.container) {
       moveItemInArray(
         event.container.data,
         event.previousIndex,
         event.currentIndex
       );
-      console.warn(event.previousContainer);
       this.updateSpellsInDatabase(this.getIndex(event.previousContainer.id));
       // update database with one level
     } else {

+ 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());
   }
 }

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

@@ -2,9 +2,12 @@
   @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">
@@ -21,12 +24,11 @@
         *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>
@@ -45,10 +47,3 @@
   </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 {

+ 44 - 36
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,12 +30,12 @@ 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 {
@@ -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 {
@@ -117,31 +120,24 @@ export class AbilityTableComponent {
   }
 
   // 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>

+ 21 - 20
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.proficiencies;
+    // 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,12 +52,20 @@ 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 {
@@ -72,7 +74,6 @@ export class ProficienciesTableComponent {
 
   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()
+    );
   }
 }

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

@@ -42,12 +42,14 @@
     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>

+ 27 - 18
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;
@@ -36,7 +32,30 @@ export class SpellslotsComponent {
     }, 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
 
@@ -97,13 +116,10 @@ export class SpellslotsComponent {
   }
 
   public correctSpellslotsView(levelIndex: number): void {
-    console.log('levelindex: ', levelIndex);
     const totalSlots = this.spellslots[levelIndex].totalSlots;
     const usedSlots = this.spellslots[levelIndex].usedSlots;
     for (let slotIndex = 0; slotIndex < usedSlots; slotIndex++) {
       setTimeout(() => {
-        console.warn('slotIndexTrue: ', slotIndex);
-
         (
           document.getElementById(
             'checkbox' + levelIndex + '-' + slotIndex
@@ -113,8 +129,6 @@ export class SpellslotsComponent {
     }
     for (let slotIndex = usedSlots; slotIndex < totalSlots; slotIndex++) {
       setTimeout(() => {
-        console.warn('slotIndexFalse: ', slotIndex);
-
         (
           document.getElementById(
             'checkbox' + levelIndex + '-' + slotIndex
@@ -145,15 +159,10 @@ export class SpellslotsComponent {
     this.dataAccessor.kiPoints = this.kiPoints;
   }
 
-  public openSpellslotModal(): void {
-    this.ngxSmartModalService.getModal('spellslotsModal').open();
-  }
-
   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());
   }
 }

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

@@ -1,11 +1,6 @@
 <div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
   @for(trait of traits; let index = $index; track trait){
-  <div
-    class="example-box"
-    *ngFor="let trait of traits; let abilityIndex = index"
-    (click)="openDetailsPanel(abilityIndex)"
-    cdkDrag
-  >
+  <div class="example-box" (click)="openDetailsPanel(index)" cdkDrag>
     <!-- Eventuell Symbol für den Ursprung des Traits -->
     <div class="trait-name">{{ trait.name }}</div>
     <br />
@@ -25,10 +20,3 @@
   </div>
   }
 </div>
-
-<trait-modal
-  (traitCreated)="addNewlyCreatedTrait($event)"
-  (traitUpdated)="updateTrait($event)"
-  [isToUpdate]="isToUpdate"
-  [traitToUpdate]="traitToUpdate"
-></trait-modal>

+ 38 - 30
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,7 +25,7 @@ export class TraitTableComponent {
 
   public constructor(
     public dataAccessor: DataService,
-    public ngxSmartModalService: NgxSmartModalService,
+    public modalAccessor: ModalService,
     private detailsAccesssor: DetailsService
   ) {}
 
@@ -33,48 +33,50 @@ export class TraitTableComponent {
     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.traits = 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();
+  }
 }

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

@@ -63,7 +63,6 @@ export class AttributeFieldComponent {
   }
 
   public openDetails(): void {
-    console.log(this.attribute);
     this.detailsAccessor.openPanel(AttributeDetailsComponent, {
       attribute: this.attribute,
       modifier: this.attributeModifier,

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

@@ -54,10 +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/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);
     }
   }
 }

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

@@ -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();
       }

+ 5 - 3
src/app/journal/journal-stats/weapons-container/spell-table/spell-table.component.ts

@@ -69,8 +69,8 @@ export class SpellTableComponent {
         resultSubscription.unsubscribe();
         if (result.state === 'delete') {
           this.spells.splice(spellIndex, 1);
+          this.updateSpellsInDatabase();
         } else if (result.state !== 'cancel') {
-          console.log(result.state);
           throw new Error('Unexpected result state, please send a bug report.');
         }
       }
@@ -88,11 +88,12 @@ 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();
       }
@@ -111,10 +112,11 @@ 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();
       }

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

+ 0 - 1
src/app/journal/journal-stats/weapons-container/weapon-table/weapon-modal/weapon-modal.component.ts

@@ -142,7 +142,6 @@ export class WeaponModalComponent {
   }
 
   public createItem(): Weapon {
-    console.log('createItem() in weapon-modal.component.ts');
     return {
       name: this.name,
       range: this.range,

+ 2 - 1
src/app/journal/journal-stats/weapons-container/weapon-table/weapon-table.component.ts

@@ -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();
       }

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

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

@@ -4,9 +4,14 @@ 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';
 import { JournalStatsComponent } from './journal-stats/journal-stats.component';
@@ -159,9 +164,12 @@ import { AddCardComponent } from './journal-spellcards/add-card/add-card.compone
     FormsModule,
     CdkTableModule,
     DragDropModule,
-    NgxSmartModalModule.forChild(),
     SharedComponentsModule,
     ReactiveFormsModule,
+    MatSlideToggleModule,
+    MatSelectModule,
+    MatInputModule,
+    MatFormFieldModule,
   ],
 })
 export class JournalModule {}

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

@@ -165,7 +165,6 @@
     </div>
   </div>
 </div>
-<!-- </ngx-smart-modal> -->
 
 <!-- templates -->
 

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

@@ -162,6 +162,7 @@ export class SpellModalComponent {
   private createSpell(): Spell {
     const spell: Spell = {
       name: this.name,
+      duration: 0, // FIXME: only mocked
       damage: this.damage,
       heal: this.heal,
       level: parseInt(this.level.toString()),
@@ -184,8 +185,6 @@ export class SpellModalComponent {
       doesDamage: this.doesDamage,
       doesHeal: this.doesHeal,
     };
-    console.log('New Spell from modal: ', spell);
-    console.log('type of level', typeof spell.level);
     return spell;
   }
 

+ 11 - 6
src/app/shared-components/full-spellcard/full-spellcard.component.html

@@ -11,17 +11,20 @@
       </thead>
       <tbody>
         <tr>
-          <td>Benötigt verbale Komponente</td>
-          <td>{{ spell.needsVerbal }}</td>
+          <td>Komponenten:</td>
+          <td>
+            @if(spell.needsVerbal){Verbal} @if(spell.needsSomatic){, Somatic}
+            @if(spell.needsMaterial){, Material}
+          </td>
         </tr>
-        <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> -->
 
         <tr>
           <td>Stufe</td>
@@ -35,10 +38,12 @@
           <td>Kann als Ritual gewirkt werden</td>
           <td>{{ spell.canRitual }}</td>
         </tr>
-        <!-- <tr>
+        <tr>
           <td>Wirkungsdauer</td>
           <td>{{ spell.duration }}</td>
-        <tr> -->
+        </tr>
+
+        <tr></tr>
         <tr>
           <td>Benötigt Konzentration</td>
           <td>{{ spell.needsConcentration }}</td>

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

+ 5 - 1
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;
@@ -29,7 +30,10 @@ export interface Spell {
 
 // Number of targets
 // duration
-//  Material
+// 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;

+ 5 - 191
src/services/data/data.service.ts

@@ -26,7 +26,6 @@ export class DataService {
   }
 
   async loadData(): Promise<any> {
-    console.warn('loadData() called');
     this.characterName = sessionStorage.getItem('characterName')!;
     if (this.dataLoaded) {
       return Promise.resolve();
@@ -40,7 +39,6 @@ export class DataService {
   // #region character selection and construction
 
   private buildCurrentCharacter(currentCharacterData: any): void {
-    console.log('currentCharacterData: ', currentCharacterData);
     const [
       abilitiesData,
       appearanceData,
@@ -73,12 +71,6 @@ export class DataService {
       weaponsAndArmorData,
     ] = currentCharacterData.map((entry: any) => entry.data);
 
-    // currentCharacterData.forEach((entry: any, index: number) => {
-    //   console.log(entry.key, ' : ', entry.data);
-    // });
-
-    console.warn('The whole character: ', currentCharacterData);
-
     // Attributes
     Object.keys(attributesData).forEach((key: string) => {
       this.updateAttribute(attributesData[key]);
@@ -152,81 +144,7 @@ export class DataService {
     this.setData('favoriteSpells', { spells: 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,
-    },
-  ];
+  private _spellLevel0: Spell[] = [];
 
   public get spellLevel0(): Spell[] {
     return this._spellLevel0;
@@ -237,56 +155,7 @@ export class DataService {
     this.setData('spellLevel0', { spells: spells });
   }
 
-  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,
-    },
-  ];
+  private _spellLevel1: Spell[] = [];
 
   public get spellLevel1(): Spell[] {
     return this._spellLevel1;
@@ -297,60 +166,7 @@ export class DataService {
     this.setData('spellLevel1', { spells: spells });
   }
 
-  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,
-    },
-  ];
+  private _spellLevel2: Spell[] = [];
 
   public get spellLevel2(): Spell[] {
     return this._spellLevel2;
@@ -465,7 +281,7 @@ export class DataService {
 
   public set abilities(abilities: Ability[]) {
     this._abilities = abilities;
-    console.warn('abilities: ', abilities);
+    this.setData('abilities', { data: abilities });
   }
 
   private _traits: Trait[] = [];
@@ -689,7 +505,6 @@ export class DataService {
       wisdom: this.wisdomSubject.getValue(),
       charisma: this.charismaSubject.getValue(),
     };
-    // TODO: ACTIVATE AGAIN
     this.setData('attributes', attributesData);
   }
 
@@ -750,8 +565,7 @@ export class DataService {
       stealth: this.stealthSubject.getValue(),
       survival: this.survivalSubject.getValue(),
     };
-    // TODO: ACTIVATE AGAIN
-    // this.setData('skills', skillsData);
+    this.setData('skills', skillsData);
   }
 
   private acrobaticsSubject = new BehaviorSubject<Skill>(

+ 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');

+ 123 - 28
src/services/spells/spells.service.ts

@@ -24,11 +24,14 @@ export class SpellsService {
   ];
 
   private level1: string[] = [
-    'Heilende Berührung (Cure Wounds)',
+    'Wunden Heilen (Cure Wounds)',
     'Magie entdecken (Detect Magic)',
-    'Segen (Bless)',
+    '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[] = [];
@@ -82,29 +85,36 @@ export class SpellsService {
         return this.tollTheDead;
       case 'Thaumaturgie (Thaumaturgy)':
         return this.thaumaturgie;
-      case 'Heilende Berührung (Cure Wounds)':
+      case 'Wunden Heilen (Cure Wounds)':
         return this.cureWounds;
       case 'Magie entdecken (Detect Magic)':
         return this.detectMagic;
-      case 'Segen (Bless)':
+      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: 'Führung',
+    name: 'Göttliche Führung',
     level: 0,
     cost: 'action',
+    duration: 10,
     canRitual: false,
     needsVerbal: true,
     needsSomatic: true,
-    needsMaterial: true,
+    needsMaterial: false,
     needsConcentration: true,
     needsAttackRoll: false,
     needsSavingThrow: false,
@@ -113,8 +123,8 @@ export class SpellsService {
     doesHeal: false,
     heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
     description:
-      'Du berührst eine Kreatur und für eine Minute erhält sie einen Bonus von 1W4 auf Angriffs- und Rettungswürfe.',
-    school: 'enchantment',
+      '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,
@@ -126,10 +136,11 @@ export class SpellsService {
     name: 'Totenläuten',
     level: 0,
     cost: 'action',
+    duration: 0,
     canRitual: false,
     needsVerbal: true,
-    needsSomatic: false,
-    needsMaterial: true,
+    needsSomatic: true,
+    needsMaterial: false,
     needsConcentration: false,
     needsAttackRoll: false,
     needsSavingThrow: true,
@@ -139,7 +150,7 @@ export class SpellsService {
     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.',
+      '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,
@@ -152,6 +163,7 @@ export class SpellsService {
     name: 'Thaumaturgie',
     level: 0,
     cost: 'action',
+    duration: 10,
     canRitual: false,
     needsVerbal: true,
     needsSomatic: false,
@@ -166,8 +178,8 @@ export class SpellsService {
     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: false,
-    range: 9,
+    isRanged: true,
+    range: 30,
     hasAreaOfEffect: false,
     areaOfEffectType: '',
     radius: 0,
@@ -177,6 +189,7 @@ export class SpellsService {
     name: 'Heilende Berührung',
     level: 1,
     cost: 'action',
+    duration: 0,
     canRitual: false,
     needsVerbal: true,
     needsSomatic: true,
@@ -187,9 +200,9 @@ export class SpellsService {
     doesDamage: false,
     damage: [{ diceNumber: '', diceType: '', damageType: '' }],
     doesHeal: true,
-    heal: { diceNumber: '1', diceType: 'd8', additionalHeal: 5 },
+    heal: { diceNumber: '1', diceType: 'd8', additionalHeal: 4 },
     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.',
+      '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,
@@ -202,10 +215,11 @@ export class SpellsService {
     name: 'Magie entdecken',
     level: 1,
     cost: 'action',
+    duration: 100,
     canRitual: true,
     needsVerbal: true,
     needsSomatic: true,
-    needsMaterial: true,
+    needsMaterial: false,
     needsConcentration: false,
     needsAttackRoll: false,
     needsSavingThrow: false,
@@ -214,9 +228,9 @@ export class SpellsService {
     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.',
+      '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: false,
+    isRanged: true,
     range: 30,
     hasAreaOfEffect: false,
     areaOfEffectType: '',
@@ -224,9 +238,10 @@ export class SpellsService {
   };
 
   private bless: Spell = {
-    name: 'Segen',
+    name: 'Segnen',
     level: 1,
     cost: 'action',
+    duration: 10,
     canRitual: false,
     needsVerbal: true,
     needsSomatic: true,
@@ -239,10 +254,10 @@ export class SpellsService {
     doesHeal: false,
     heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
     description:
-      'Du segnest bis zu drei Kreaturen deiner Wahl innerhalb von 9 Metern. Jedes Mal, wenn ein Ziel einen Angriffs- oder Rettungswurf macht, bevor der Zauber endet, kann das Ziel einen Würfelwurf von 1W4 auf den Wurf machen und den Wurf um die gewürfelte Zahl erhöhen.',
+      '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: false,
-    range: 9,
+    isRanged: true,
+    range: 60,
     hasAreaOfEffect: false,
     areaOfEffectType: '',
     radius: 0,
@@ -252,6 +267,7 @@ export class SpellsService {
     name: 'Heilendes Wort',
     level: 1,
     cost: 'bonus action',
+    duration: 0,
     canRitual: false,
     needsVerbal: true,
     needsSomatic: false,
@@ -262,12 +278,12 @@ export class SpellsService {
     doesDamage: false,
     damage: [{ diceNumber: '', diceType: '', damageType: '' }],
     doesHeal: true,
-    heal: { diceNumber: '1', diceType: 'd4', additionalHeal: 0 },
+    heal: { diceNumber: '1', diceType: 'd4', additionalHeal: 4 },
     description:
-      'Ein Wort der Heilung flüstert von dir und berührt bis zu sechs Kreaturen deiner Wahl innerhalb von 18 Metern. Jede Kreatur erhält 1W4 + deinen Fertigkeitsbonus Trefferpunkte.',
+      '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: 90,
+    range: 60,
     hasAreaOfEffect: false,
     areaOfEffectType: '',
     radius: 0,
@@ -277,24 +293,103 @@ export class SpellsService {
     name: 'Lenkendes Geschoss',
     level: 1,
     cost: 'action',
+    duration: 0,
     canRitual: false,
-    needsVerbal: false,
+    needsVerbal: true,
     needsSomatic: true,
     needsMaterial: false,
     needsConcentration: false,
     needsAttackRoll: true,
     needsSavingThrow: false,
     doesDamage: true,
-    damage: [{ diceNumber: '3', diceType: 'd4', damageType: 'force' }],
+    damage: [{ diceNumber: '4', diceType: 'd6', damageType: 'radiant' }],
     doesHeal: false,
     heal: { diceNumber: '', diceType: '', additionalHeal: 0 },
     description:
-      'Du schießt drei magische Geschosse auf Kreaturen oder Objekte innerhalb von 30 Fuß. Du kannst sie auf ein Ziel oder mehrere verteilen. Du kannst auch mehrere Geschosse auf ein Ziel abfeuern. Mache für jedes Geschoss einen Angriffswurf. Ein Geschoss trifft und verursacht 1W4 + 1 Kraftschaden.',
+      '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,
+  };
 }

+ 12 - 6
src/styles.scss

@@ -4,12 +4,6 @@
 // TODO: remove bootstrap styles
 @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 {
     // COLORS
     --primary-color: #d8ac96;
@@ -31,6 +25,7 @@ $dialog-position-right: 20%;
 
     --border-color: #8d8c8c;
 
+    // OFFICIAL COLORS
     --accept: #84a36f;
     --accept-hover: #6f9158;
     --delete: #a45a52;
@@ -38,6 +33,8 @@ $dialog-position-right: 20%;
     --edit: #86a5b7;
     --edit-hover: #6f8e9f;
 
+    --modal-background: antiquewhite;
+
     // shadows
     --shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.5);
     --shadow-small: 4px 4px 10px 4px rgba(0, 0, 0, 0.2);
@@ -432,3 +429,12 @@ input[type="checkbox"] {
 }
 
 // Drag and Drop Table
+
+html,
+body {
+    height: 100%;
+}
+body {
+    margin: 0;
+    // font-family: Roboto, "Helvetica Neue", sans-serif;
+}