From c48728d7cd06da4f12a57111b10185fff9f547cb Mon Sep 17 00:00:00 2001 From: Marc Fokkert Date: Fri, 15 Nov 2024 16:51:15 +0100 Subject: [PATCH] Add basic API connection to Grocy Add initial table to show entries --- angular.json | 1 + package-lock.json | 787 ++++++++++++------ package.json | 7 + .../grocy-config-dialog.component.css | 0 .../grocy-config-dialog.component.html | 15 + .../grocy-config-dialog.component.spec.ts | 30 + .../grocy-config-dialog.component.ts | 57 ++ src/app/api/grocy-rest.service.spec.ts | 16 + src/app/api/grocy-rest.service.ts | 80 ++ src/app/api/grocy.ts | 2 + src/app/app.component.html | 335 +------- src/app/app.component.ts | 3 +- src/app/app.config.ts | 10 +- src/app/batch-scan-table/barcode.ts | 6 + .../batch-scan-table.component.css | 0 .../batch-scan-table.component.html | 29 + .../batch-scan-table.component.spec.ts | 23 + .../batch-scan-table.component.ts | 65 ++ .../new-product-handle.component.css | 0 .../new-product-handle.component.html | 3 + .../new-product-handle.component.spec.ts | 23 + .../new-product-handle.component.ts | 42 + .../plus-button/plus-button.component.css | 0 .../plus-button/plus-button.component.html | 5 + .../plus-button/plus-button.component.spec.ts | 23 + .../plus-button/plus-button.component.ts | 10 + .../table-row-image-dialog.component.css | 0 .../table-row-image-dialog.component.html | 9 + .../table-row-image-dialog.component.spec.ts | 23 + .../table-row-image-dialog.component.ts | 19 + .../table-row-image.component.css | 0 .../table-row-image.component.html | 5 + .../table-row-image.component.spec.ts | 23 + .../table-row-image.component.ts | 28 + src/app/tailwindui/tailwindui.module.ts | 26 + src/custom-theme.scss | 41 + src/index.html | 3 + src/styles.css | 7 +- tailwind.config.js | 18 + 39 files changed, 1197 insertions(+), 577 deletions(-) create mode 100644 src/app/api/grocy-config-dialog/grocy-config-dialog.component.css create mode 100644 src/app/api/grocy-config-dialog/grocy-config-dialog.component.html create mode 100644 src/app/api/grocy-config-dialog/grocy-config-dialog.component.spec.ts create mode 100644 src/app/api/grocy-config-dialog/grocy-config-dialog.component.ts create mode 100644 src/app/api/grocy-rest.service.spec.ts create mode 100644 src/app/api/grocy-rest.service.ts create mode 100644 src/app/api/grocy.ts create mode 100644 src/app/batch-scan-table/barcode.ts create mode 100644 src/app/batch-scan-table/batch-scan-table.component.css create mode 100644 src/app/batch-scan-table/batch-scan-table.component.html create mode 100644 src/app/batch-scan-table/batch-scan-table.component.spec.ts create mode 100644 src/app/batch-scan-table/batch-scan-table.component.ts create mode 100644 src/app/batch-scan-table/new-product-handle/new-product-handle.component.css create mode 100644 src/app/batch-scan-table/new-product-handle/new-product-handle.component.html create mode 100644 src/app/batch-scan-table/new-product-handle/new-product-handle.component.spec.ts create mode 100644 src/app/batch-scan-table/new-product-handle/new-product-handle.component.ts create mode 100644 src/app/tailwindui/plus-button/plus-button.component.css create mode 100644 src/app/tailwindui/plus-button/plus-button.component.html create mode 100644 src/app/tailwindui/plus-button/plus-button.component.spec.ts create mode 100644 src/app/tailwindui/plus-button/plus-button.component.ts create mode 100644 src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.css create mode 100644 src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.html create mode 100644 src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.spec.ts create mode 100644 src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.ts create mode 100644 src/app/tailwindui/table-row-image/table-row-image.component.css create mode 100644 src/app/tailwindui/table-row-image/table-row-image.component.html create mode 100644 src/app/tailwindui/table-row-image/table-row-image.component.spec.ts create mode 100644 src/app/tailwindui/table-row-image/table-row-image.component.ts create mode 100644 src/app/tailwindui/tailwindui.module.ts create mode 100644 src/custom-theme.scss create mode 100644 tailwind.config.js diff --git a/angular.json b/angular.json index 88d2e63..e6ec526 100644 --- a/angular.json +++ b/angular.json @@ -27,6 +27,7 @@ } ], "styles": [ + "src/custom-theme.scss", "src/styles.css" ], "scripts": [] diff --git a/package-lock.json b/package-lock.json index d96c914..40f5773 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,16 @@ "version": "0.0.0", "dependencies": { "@angular/animations": "^18.2.0", + "@angular/cdk": "^18.2.11", "@angular/common": "^18.2.0", "@angular/compiler": "^18.2.0", "@angular/core": "^18.2.0", "@angular/forms": "^18.2.0", + "@angular/material": "^18.2.11", "@angular/platform-browser": "^18.2.0", "@angular/platform-browser-dynamic": "^18.2.0", "@angular/router": "^18.2.0", + "lodash": "^4.17.21", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.14.10" @@ -25,15 +28,32 @@ "@angular/cli": "^18.2.11", "@angular/compiler-cli": "^18.2.0", "@types/jasmine": "~5.1.0", + "@types/lodash": "^4.17.13", + "autoprefixer": "^10.4.20", "jasmine-core": "~5.2.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.14", "typescript": "~5.5.2" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -193,6 +213,35 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/postcss": { + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", @@ -351,6 +400,23 @@ } } }, + "node_modules/@angular/cdk": { + "version": "18.2.11", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.11.tgz", + "integrity": "sha512-FuvfhrSz2ch0gyOVHrkWq2C/I2PnOzKYSXlG/VEG+ize/WNrvlYy//5WVrTh/hv+HD9sdoWPr9ULXsfFfgbo7w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/cli": { "version": "18.2.11", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.11.tgz", @@ -450,6 +516,36 @@ "typescript": ">=5.4 <5.6" } }, + "node_modules/@angular/compiler-cli/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular/compiler-cli/node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular/core": { "version": "18.2.10", "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.10.tgz", @@ -484,6 +580,24 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material": { + "version": "18.2.11", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.11.tgz", + "integrity": "sha512-VPfnpwmg6p5DsH1UMfOXjKA+qAbUx6nyinGWpx4+ntr/T1oEhRk5CnoOtVS0Xk0rnRSbEF6ayjDBH2YPR9ol3A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^18.0.0 || ^19.0.0", + "@angular/cdk": "18.2.11", + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "@angular/forms": "^18.0.0 || ^19.0.0", + "@angular/platform-browser": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/platform-browser": { "version": "18.2.10", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.10.tgz", @@ -4423,6 +4537,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", + "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -4962,6 +5083,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -4989,6 +5117,13 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -5474,6 +5609,16 @@ "node": ">=6" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001677", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz", @@ -5520,19 +5665,41 @@ "license": "MIT" }, "node_modules/chokidar": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", - "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 8.10.0" }, "funding": { "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/chownr": { @@ -6305,6 +6472,20 @@ "dev": true, "license": "MIT" }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -6526,7 +6707,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -8588,31 +8769,6 @@ "source-map-support": "^0.5.5" } }, - "node_modules/karma/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/karma/node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -8632,19 +8788,6 @@ "dev": true, "license": "MIT" }, - "node_modules/karma/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/karma/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -8655,32 +8798,6 @@ "node": ">=8" } }, - "node_modules/karma/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/karma/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/karma/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -8907,6 +9024,16 @@ } } }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -9065,7 +9192,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, "license": "MIT" }, "node_modules/lodash.debounce": { @@ -9763,6 +9889,18 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -10193,6 +10331,16 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -10540,7 +10688,7 @@ "version": "7.2.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "entities": "^4.5.0" @@ -10699,6 +10847,16 @@ "node": ">=6" } }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/piscina": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.6.1.tgz", @@ -10726,9 +10884,9 @@ } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -10747,13 +10905,100 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/postcss-loader": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", @@ -10856,6 +11101,32 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, "node_modules/postcss-selector-parser": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", @@ -11037,6 +11308,26 @@ "node": ">= 0.8" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -11053,17 +11344,29 @@ } }, "node_modules/readdirp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", - "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14.16.0" + "node": ">=8.6" }, "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/reflect-metadata": { @@ -11496,70 +11799,6 @@ } } }, - "node_modules/sass/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/sass/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/sass/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/sass/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", @@ -12393,6 +12632,86 @@ "node": ">=6" } }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12429,6 +12748,44 @@ "node": ">=0.10" } }, + "node_modules/tailwindcss": { + "version": "3.4.14", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz", + "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -12620,6 +12977,29 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/thingies": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", @@ -12703,6 +13083,13 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -13508,35 +13895,6 @@ "@esbuild/win32-x64": "0.21.5" } }, - "node_modules/vite/node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", @@ -13735,31 +14093,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/webpack-dev-server/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/webpack-dev-server/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -13781,19 +14114,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/webpack-dev-server/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", @@ -13835,32 +14155,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/webpack-dev-server/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/webpack-dev-server/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/webpack-dev-server/node_modules/rimraf": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", @@ -14173,6 +14467,19 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 5dc8ce0..0ab2afc 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,16 @@ "private": true, "dependencies": { "@angular/animations": "^18.2.0", + "@angular/cdk": "^18.2.11", "@angular/common": "^18.2.0", "@angular/compiler": "^18.2.0", "@angular/core": "^18.2.0", "@angular/forms": "^18.2.0", + "@angular/material": "^18.2.11", "@angular/platform-browser": "^18.2.0", "@angular/platform-browser-dynamic": "^18.2.0", "@angular/router": "^18.2.0", + "lodash": "^4.17.21", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.14.10" @@ -27,12 +30,16 @@ "@angular/cli": "^18.2.11", "@angular/compiler-cli": "^18.2.0", "@types/jasmine": "~5.1.0", + "@types/lodash": "^4.17.13", + "autoprefixer": "^10.4.20", "jasmine-core": "~5.2.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.14", "typescript": "~5.5.2" } } diff --git a/src/app/api/grocy-config-dialog/grocy-config-dialog.component.css b/src/app/api/grocy-config-dialog/grocy-config-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/api/grocy-config-dialog/grocy-config-dialog.component.html b/src/app/api/grocy-config-dialog/grocy-config-dialog.component.html new file mode 100644 index 0000000..45f44f2 --- /dev/null +++ b/src/app/api/grocy-config-dialog/grocy-config-dialog.component.html @@ -0,0 +1,15 @@ +

Setup grocy config

+ + + URI + + + + API key + + + + + + + diff --git a/src/app/api/grocy-config-dialog/grocy-config-dialog.component.spec.ts b/src/app/api/grocy-config-dialog/grocy-config-dialog.component.spec.ts new file mode 100644 index 0000000..5747b9f --- /dev/null +++ b/src/app/api/grocy-config-dialog/grocy-config-dialog.component.spec.ts @@ -0,0 +1,30 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GrocyConfigDialogComponent } from './grocy-config-dialog.component'; + +describe('GrocyConfigDialogComponent', () => { + let component: GrocyConfigDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [GrocyConfigDialogComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(GrocyConfigDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should clean the uri correctly', () => { + expect(component.cleanUri('https://grocy.test.me/api/')).toEqual('https://grocy.test.me') + expect(component.cleanUri('https://grocy.test.me/api')).toEqual('https://grocy.test.me') + expect(component.cleanUri('https://grocy.test.me/')).toEqual('https://grocy.test.me') + expect(component.cleanUri('https://grocy.test.me')).toEqual('https://grocy.test.me') + }) +}); diff --git a/src/app/api/grocy-config-dialog/grocy-config-dialog.component.ts b/src/app/api/grocy-config-dialog/grocy-config-dialog.component.ts new file mode 100644 index 0000000..11ebfb0 --- /dev/null +++ b/src/app/api/grocy-config-dialog/grocy-config-dialog.component.ts @@ -0,0 +1,57 @@ +import {Component, inject, OnInit} from '@angular/core'; +import { + MAT_DIALOG_DATA, + MatDialogActions, + MatDialogContent, + MatDialogRef, + MatDialogTitle +} from '@angular/material/dialog'; +import {NgOptimizedImage} from '@angular/common'; +import {MatFormFieldModule} from '@angular/material/form-field'; +import {MatInputModule} from '@angular/material/input'; +import {LS_GROCY_API_KEY, LS_GROCY_URI} from '../grocy'; +import {FormsModule} from '@angular/forms'; +import {MatButton} from '@angular/material/button'; + +@Component({ + selector: 'app-grocy-config-dialog', + standalone: true, + imports: [ + MatDialogTitle, + MatDialogContent, + NgOptimizedImage, + MatFormFieldModule, + MatInputModule, + FormsModule, + MatDialogActions, + MatButton + ], + templateUrl: './grocy-config-dialog.component.html', + styleUrl: './grocy-config-dialog.component.css' +}) +export class GrocyConfigDialogComponent implements OnInit { + readonly dialogRef = inject(MatDialogRef); + host = window.location.host; + + uri?: string; + apiKey?: string; + + + ngOnInit(): void { + this.uri = localStorage.getItem(LS_GROCY_URI) ?? ''; + this.apiKey = localStorage.getItem(LS_GROCY_API_KEY) ?? ''; + } + + cleanUri(uri: string): string { + uri = uri.replace(/\/+api\/?$/i, ''); + uri = uri.replace(/\/$/, ''); + + return uri; + } + + save() { + localStorage.setItem(LS_GROCY_URI, this.uri ?? '') + localStorage.setItem(LS_GROCY_API_KEY, this.apiKey ?? '') + this.dialogRef.close(true); + } +} diff --git a/src/app/api/grocy-rest.service.spec.ts b/src/app/api/grocy-rest.service.spec.ts new file mode 100644 index 0000000..ea71765 --- /dev/null +++ b/src/app/api/grocy-rest.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { GrocyRestService } from './grocy-rest.service'; + +describe('GrocyRestService', () => { + let service: GrocyRestService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(GrocyRestService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/api/grocy-rest.service.ts b/src/app/api/grocy-rest.service.ts new file mode 100644 index 0000000..499d1e7 --- /dev/null +++ b/src/app/api/grocy-rest.service.ts @@ -0,0 +1,80 @@ +import {inject, Injectable} from '@angular/core'; +import {HttpClient, HttpHeaders, HttpResponse} from '@angular/common/http'; +import {MatDialog} from '@angular/material/dialog'; +import {GrocyConfigDialogComponent} from './grocy-config-dialog/grocy-config-dialog.component'; +import {map, Observable, of, switchMap, tap} from 'rxjs'; +import _ from 'lodash'; +import {LS_GROCY_API_KEY, LS_GROCY_URI} from './grocy'; +import {BatchScanRow} from '../batch-scan-table/barcode'; + +const GROCY_API_HEADER = 'GROCY-API-KEY' + +@Injectable({ + providedIn: 'root' +}) +export class GrocyRestService { + readonly http = inject(HttpClient); + readonly dialog = inject(MatDialog); + + + private areRequiredKeysPresent(): boolean { + const requiredKeys = [LS_GROCY_API_KEY, LS_GROCY_URI] + + return !!requiredKeys + .map(key => localStorage.getItem(key)) + .reduce((c, a) => c && a) + } + + private ensureApiKeys(): Observable { + if (!this.areRequiredKeysPresent()) { + const dialogRef = this.dialog.open(GrocyConfigDialogComponent) + return dialogRef.afterClosed() + } + return of(true) + } + + get(uri: string): Observable> { + return this.ensureApiKeys().pipe( + switchMap(() => { + let headers = new HttpHeaders(); + const baseUri = localStorage.getItem(LS_GROCY_URI); + + headers = headers.set(GROCY_API_HEADER, localStorage.getItem(LS_GROCY_API_KEY)!) + + return this.http.get(`${baseUri}/api${uri}`, { + headers, + observe: "response" + }) + }) + ) + } + + getProductIdByBarcode(barcode: string): Observable { + return this.get(`/stock/products/by-barcode/${barcode}`).pipe( + map(response => { + if (response.ok) { + return _.get(response.body, 'product.id') as unknown as string; + } + + return null; + }) + ) + } + + goToGrocyProductPageUri(productId: string) { + const baseUri = localStorage.getItem(LS_GROCY_URI); + if (window) { + window.open(`${baseUri}/product/${productId}`, '_blank')?.focus(); + } + } + + getProductId(row: BatchScanRow): Observable { + if(row.productId) { + return of(row.productId); + } + + return this.getProductIdByBarcode(row.barcode).pipe( + tap(productId => row.productId = row.productId ?? productId) + ) + } +} diff --git a/src/app/api/grocy.ts b/src/app/api/grocy.ts new file mode 100644 index 0000000..faee375 --- /dev/null +++ b/src/app/api/grocy.ts @@ -0,0 +1,2 @@ +export const LS_GROCY_API_KEY = 'grocy-api-key' +export const LS_GROCY_URI = 'grocy-uri' diff --git a/src/app/app.component.html b/src/app/app.component.html index 36093e1..0ee3b77 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,336 +1,3 @@ - - - - - - - - - - - -
-
-
- -

Hello, {{ title }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 6d61bf2..acada0e 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,10 +1,11 @@ import { Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; +import {BatchScanTableComponent} from './batch-scan-table/batch-scan-table.component'; @Component({ selector: 'app-root', standalone: true, - imports: [RouterOutlet], + imports: [RouterOutlet, BatchScanTableComponent], templateUrl: './app.component.html', styleUrl: './app.component.css' }) diff --git a/src/app/app.config.ts b/src/app/app.config.ts index a1e7d6f..144702f 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -2,7 +2,15 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; +import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; +import {provideHttpClient} from '@angular/common/http'; export const appConfig: ApplicationConfig = { - providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)] + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + provideAnimationsAsync(), + provideHttpClient() + + ] }; diff --git a/src/app/batch-scan-table/barcode.ts b/src/app/batch-scan-table/barcode.ts new file mode 100644 index 0000000..8bd63df --- /dev/null +++ b/src/app/batch-scan-table/barcode.ts @@ -0,0 +1,6 @@ +export interface BatchScanRow { + barcode: string; + image: string; + productId: string; +} + diff --git a/src/app/batch-scan-table/batch-scan-table.component.css b/src/app/batch-scan-table/batch-scan-table.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/batch-scan-table/batch-scan-table.component.html b/src/app/batch-scan-table/batch-scan-table.component.html new file mode 100644 index 0000000..7cf523e --- /dev/null +++ b/src/app/batch-scan-table/batch-scan-table.component.html @@ -0,0 +1,29 @@ + + + Barcode + + {{row.barcode}} + + + + + + Quantity + {{row.quantity}} + + + + Product Name + {{row.productName}} + + + + Image + + + + + + + + diff --git a/src/app/batch-scan-table/batch-scan-table.component.spec.ts b/src/app/batch-scan-table/batch-scan-table.component.spec.ts new file mode 100644 index 0000000..7e1f9f5 --- /dev/null +++ b/src/app/batch-scan-table/batch-scan-table.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BatchScanTableComponent } from './batch-scan-table.component'; + +describe('BatchScanTableComponent', () => { + let component: BatchScanTableComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [BatchScanTableComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(BatchScanTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/batch-scan-table/batch-scan-table.component.ts b/src/app/batch-scan-table/batch-scan-table.component.ts new file mode 100644 index 0000000..aaa4acd --- /dev/null +++ b/src/app/batch-scan-table/batch-scan-table.component.ts @@ -0,0 +1,65 @@ +import {Component, inject} from '@angular/core'; +import {MatTableModule} from '@angular/material/table'; +import {AsyncPipe, NgOptimizedImage} from '@angular/common'; +import {TailwinduiModule} from '../tailwindui/tailwindui.module'; +import {GrocyRestService} from '../api/grocy-rest.service'; +import {catchError, map, Observable, of, tap} from 'rxjs'; +import {BatchScanRow} from './barcode'; +import {NewProductHandleComponent} from './new-product-handle/new-product-handle.component'; + +@Component({ + selector: 'app-batch-scan-table', + standalone: true, + imports: [ + MatTableModule, + NgOptimizedImage, + TailwinduiModule, + AsyncPipe, + NewProductHandleComponent + ], + templateUrl: './batch-scan-table.component.html', + styleUrl: './batch-scan-table.component.css' +}) +export class BatchScanTableComponent { + grocy = inject(GrocyRestService) + + imgSizePx = 20; + + displayedColumns = ['barcode', 'quantity', 'productName', 'image'] + + dataSource = [ + { + barcode: '8712000032920', + image: 'https://images.openfoodfacts.org/images/products/871/200/003/2920/front_en.7.400.jpg' + } , { + barcode: '8712000056650', + image: '' + } + ] + + handleBarcodeClick(row: BatchScanRow) { + if (!row.barcode) { + return; + } + + this.getProductId(row).subscribe(productId => { + if (productId) { + this.grocy.goToGrocyProductPageUri(productId) + } + }) + + + } + + private getProductId(row: BatchScanRow): Observable { + if(row.productId) { + return of(row.productId); + } + + return this.grocy.getProductIdByBarcode(row.barcode).pipe( + tap(productId => row.productId = row.productId ?? productId) + ) + } + + +} diff --git a/src/app/batch-scan-table/new-product-handle/new-product-handle.component.css b/src/app/batch-scan-table/new-product-handle/new-product-handle.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/batch-scan-table/new-product-handle/new-product-handle.component.html b/src/app/batch-scan-table/new-product-handle/new-product-handle.component.html new file mode 100644 index 0000000..3cf1c86 --- /dev/null +++ b/src/app/batch-scan-table/new-product-handle/new-product-handle.component.html @@ -0,0 +1,3 @@ +@if (isProductNew$ | async) { + NEW +} diff --git a/src/app/batch-scan-table/new-product-handle/new-product-handle.component.spec.ts b/src/app/batch-scan-table/new-product-handle/new-product-handle.component.spec.ts new file mode 100644 index 0000000..32d7edb --- /dev/null +++ b/src/app/batch-scan-table/new-product-handle/new-product-handle.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NewProductHandleComponent } from './new-product-handle.component'; + +describe('NewProductHandleComponent', () => { + let component: NewProductHandleComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NewProductHandleComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NewProductHandleComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/batch-scan-table/new-product-handle/new-product-handle.component.ts b/src/app/batch-scan-table/new-product-handle/new-product-handle.component.ts new file mode 100644 index 0000000..49ec0db --- /dev/null +++ b/src/app/batch-scan-table/new-product-handle/new-product-handle.component.ts @@ -0,0 +1,42 @@ +import {Component, inject, Input, OnChanges, SimpleChanges} from '@angular/core'; +import {BatchScanRow} from '../barcode'; +import {catchError, map, Observable, of, tap} from 'rxjs'; +import {GrocyRestService} from '../../api/grocy-rest.service'; +import {AsyncPipe} from '@angular/common'; + +@Component({ + selector: 'app-new-product-handle', + standalone: true, + imports: [ + AsyncPipe + ], + templateUrl: './new-product-handle.component.html', + styleUrl: './new-product-handle.component.css' +}) +export class NewProductHandleComponent implements OnChanges { + grocy = inject(GrocyRestService) + + @Input() row?: BatchScanRow + + isProductNew$ = this.getIsProductNew$(this.row); + + + ngOnChanges(changes: SimpleChanges) { + if (changes['row']) { + this.isProductNew$ = this.getIsProductNew$(this.row); + } + } + + getIsProductNew$(row?: BatchScanRow): Observable { + if (!row) { + return of(false); + } + + return this.grocy.getProductId(row).pipe( + map(result => !result), + catchError(() => of(true)) + ) + } + + +} diff --git a/src/app/tailwindui/plus-button/plus-button.component.css b/src/app/tailwindui/plus-button/plus-button.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/tailwindui/plus-button/plus-button.component.html b/src/app/tailwindui/plus-button/plus-button.component.html new file mode 100644 index 0000000..432cf6b --- /dev/null +++ b/src/app/tailwindui/plus-button/plus-button.component.html @@ -0,0 +1,5 @@ + diff --git a/src/app/tailwindui/plus-button/plus-button.component.spec.ts b/src/app/tailwindui/plus-button/plus-button.component.spec.ts new file mode 100644 index 0000000..ccf2379 --- /dev/null +++ b/src/app/tailwindui/plus-button/plus-button.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PlusButtonComponent } from './plus-button.component'; + +describe('PlusButtonComponent', () => { + let component: PlusButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PlusButtonComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PlusButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tailwindui/plus-button/plus-button.component.ts b/src/app/tailwindui/plus-button/plus-button.component.ts new file mode 100644 index 0000000..ed8616f --- /dev/null +++ b/src/app/tailwindui/plus-button/plus-button.component.ts @@ -0,0 +1,10 @@ +import {Component, Input} from '@angular/core'; + +@Component({ + selector: 'app-plus-button', + templateUrl: './plus-button.component.html', + styleUrl: './plus-button.component.css' +}) +export class PlusButtonComponent { + @Input() size: number = 20; +} diff --git a/src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.css b/src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.html b/src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.html new file mode 100644 index 0000000..13c35c5 --- /dev/null +++ b/src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.html @@ -0,0 +1,9 @@ +

Image for {{data.alt}}

+ +
+ @if (data.imageSrc) { + + } +
+ +
diff --git a/src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.spec.ts b/src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.spec.ts new file mode 100644 index 0000000..02f252f --- /dev/null +++ b/src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TableRowImageDialogComponent } from './table-row-image-dialog.component'; + +describe('TableRowImageDialogComponent', () => { + let component: TableRowImageDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TableRowImageDialogComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TableRowImageDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.ts b/src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.ts new file mode 100644 index 0000000..e279368 --- /dev/null +++ b/src/app/tailwindui/table-row-image/table-row-image-dialog/table-row-image-dialog.component.ts @@ -0,0 +1,19 @@ +import {Component, inject} from '@angular/core'; +import {MAT_DIALOG_DATA} from '@angular/material/dialog'; + +export interface TableRowImageDialogComponentData { + alt: string; + imageSrc?: string; +} + +@Component({ + selector: 'app-table-row-image-dialog', + templateUrl: './table-row-image-dialog.component.html', + styleUrl: './table-row-image-dialog.component.css' +}) +export class TableRowImageDialogComponent { + readonly data: TableRowImageDialogComponentData = inject(MAT_DIALOG_DATA); + + + +} diff --git a/src/app/tailwindui/table-row-image/table-row-image.component.css b/src/app/tailwindui/table-row-image/table-row-image.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/tailwindui/table-row-image/table-row-image.component.html b/src/app/tailwindui/table-row-image/table-row-image.component.html new file mode 100644 index 0000000..ab150fa --- /dev/null +++ b/src/app/tailwindui/table-row-image/table-row-image.component.html @@ -0,0 +1,5 @@ +@if (imageSrc) { + +} @else { + +} diff --git a/src/app/tailwindui/table-row-image/table-row-image.component.spec.ts b/src/app/tailwindui/table-row-image/table-row-image.component.spec.ts new file mode 100644 index 0000000..52b011a --- /dev/null +++ b/src/app/tailwindui/table-row-image/table-row-image.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TableRowImageComponent } from './table-row-image.component'; + +describe('TableRowImageComponent', () => { + let component: TableRowImageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TableRowImageComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TableRowImageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tailwindui/table-row-image/table-row-image.component.ts b/src/app/tailwindui/table-row-image/table-row-image.component.ts new file mode 100644 index 0000000..7af8c8d --- /dev/null +++ b/src/app/tailwindui/table-row-image/table-row-image.component.ts @@ -0,0 +1,28 @@ +import {Component, inject, Input} from '@angular/core'; +import {MatDialog} from '@angular/material/dialog'; +import { + TableRowImageDialogComponent, + TableRowImageDialogComponentData +} from './table-row-image-dialog/table-row-image-dialog.component'; + +@Component({ + selector: 'app-table-row-image', + templateUrl: './table-row-image.component.html', + styleUrl: './table-row-image.component.css' +}) +export class TableRowImageComponent { + readonly dialog = inject(MatDialog); + + @Input() imgSizePx: number = 20; + @Input() imageSrc?: string; + @Input() alt?: string; + + openDialog() { + this.dialog.open(TableRowImageDialogComponent, { + data: { + alt: this.alt, + imageSrc: this.imageSrc + } as TableRowImageDialogComponentData + }) + } +} diff --git a/src/app/tailwindui/tailwindui.module.ts b/src/app/tailwindui/tailwindui.module.ts new file mode 100644 index 0000000..ec7994c --- /dev/null +++ b/src/app/tailwindui/tailwindui.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import {CommonModule, NgOptimizedImage} from '@angular/common'; +import {PlusButtonComponent} from './plus-button/plus-button.component'; +import {TableRowImageComponent} from './table-row-image/table-row-image.component'; +import {TableRowImageDialogComponent} from './table-row-image/table-row-image-dialog/table-row-image-dialog.component'; +import {MatDialogModule} from '@angular/material/dialog'; + + + +@NgModule({ + declarations: [ + PlusButtonComponent, + TableRowImageComponent, + TableRowImageDialogComponent + ], + imports: [ + CommonModule, + NgOptimizedImage, + MatDialogModule + ], + exports: [ + PlusButtonComponent, + TableRowImageComponent + ] +}) +export class TailwinduiModule { } diff --git a/src/custom-theme.scss b/src/custom-theme.scss new file mode 100644 index 0000000..2e37114 --- /dev/null +++ b/src/custom-theme.scss @@ -0,0 +1,41 @@ + +// Custom Theming for Angular Material +// For more information: https://material.angular.io/guide/theming +@use '@angular/material' as mat; +// Plus imports for other components in your app. + +// Include the common styles for Angular Material. We include this here so that you only +// have to load a single css file for Angular Material in your app. +// Be sure that you only ever include this mixin once! +@include mat.core(); + +// Define the theme object. +$grocy-angular-theme: mat.define-theme(( + color: ( + theme-type: light, + primary: mat.$azure-palette, + tertiary: mat.$blue-palette, + ), + density: ( + scale: 0, + ) +)); + +// Include theme styles for core and each component used in your app. +// Alternatively, you can import and @include the theme mixins for each component +// that you are using. +:root { + //@include mat.all-component-themes($grocy-angular-theme); +} + +// Comment out the line below if you want to use the pre-defined typography utility classes. +// For more information: https://material.angular.io/guide/typography#using-typography-styles-in-your-application. +// @include mat.typography-hierarchy($grocy-angular-theme); + +// Comment out the line below if you want to use the deprecated `color` inputs. +// @include mat.color-variants-backwards-compatibility($grocy-angular-theme); + + +mat-header-row.min-w-full { + min-height: unset; +} diff --git a/src/index.html b/src/index.html index a15d8e9..06c9d28 100644 --- a/src/index.html +++ b/src/index.html @@ -6,6 +6,9 @@ + + + diff --git a/src/styles.css b/src/styles.css index 90d4ee0..6df3509 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1 +1,6 @@ -/* You can add global styles to this file, and also import other style files */ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..6b36ce8 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,18 @@ +/** @type {import('tailwindcss').Config} */ + +const defaultTheme = require('tailwindcss/defaultTheme') + +module.exports = { + content: [ + "./src/**/*.{html,ts}" + ], + theme: { + extend: { + fontFamily: { + sans: ['InterVariable', ...defaultTheme.fontFamily.sans], + }, + }, + }, + plugins: [], +} +