diff --git a/firestore.rules b/firestore.rules index 7fafa93..9275e74 100644 --- a/firestore.rules +++ b/firestore.rules @@ -17,6 +17,10 @@ service cloud.firestore { allow read: if request.auth.uid != null; } + match /guest/{guestId} { + allow read: if true; + allow write: if request.auth.uid != null; + } match /songs/{song} { allow read: if request.auth.uid != null; allow write: if request.auth.uid != null; diff --git a/package-lock.json b/package-lock.json index d16de09..fd1f0a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "firebase": "^10.8.0", "lodash": "^4.17.21", "ngx-mat-select-search": "^7.0.5", + "qrcode": "^1.5.3", "rxfire": "^6.0.5", "rxjs": "~7.8.1", "swiper": "^11.0.6", @@ -9795,6 +9796,14 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -9974,6 +9983,11 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -10236,6 +10250,11 @@ "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" }, + "node_modules/encode-utf8": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", + "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==" + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -11508,7 +11527,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -14501,7 +14519,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -16379,7 +16396,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -16391,7 +16407,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "dependencies": { "p-try": "^2.0.0" }, @@ -16434,7 +16449,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, "engines": { "node": ">=6" } @@ -16604,7 +16618,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "engines": { "node": ">=8" } @@ -16806,6 +16819,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/portfinder": { "version": "1.0.32", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", @@ -17387,6 +17408,140 @@ "node": ">=0.9" } }, + "node_modules/qrcode": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz", + "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==", + "dependencies": { + "dijkstrajs": "^1.0.1", + "encode-utf8": "^1.0.3", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qrcode/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/qrcode/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/qrcode/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/qrcode/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/qrcode/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/qrcode/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -17784,6 +17939,11 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -18353,6 +18513,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-function-length": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", @@ -20784,6 +20949,11 @@ "node": ">= 8" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + }, "node_modules/widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", @@ -28451,6 +28621,11 @@ "ms": "2.1.2" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" + }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -28583,6 +28758,11 @@ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true }, + "dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -28783,6 +28963,11 @@ "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" }, + "encode-utf8": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", + "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==" + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -29746,7 +29931,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -32022,7 +32206,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -33440,7 +33623,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "requires": { "p-limit": "^2.2.0" }, @@ -33449,7 +33631,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -33478,8 +33659,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "pac-proxy-agent": { "version": "7.0.1", @@ -33611,8 +33791,7 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, "path-is-absolute": { "version": "1.0.1", @@ -33749,6 +33928,11 @@ } } }, + "pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" + }, "portfinder": { "version": "1.0.32", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", @@ -34164,6 +34348,112 @@ "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true }, + "qrcode": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz", + "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==", + "requires": { + "dijkstrajs": "^1.0.1", + "encode-utf8": "^1.0.3", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -34481,6 +34771,11 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -34910,6 +35205,11 @@ "send": "0.18.0" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "set-function-length": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", @@ -36559,6 +36859,11 @@ "isexe": "^2.0.0" } }, + "which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + }, "widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", diff --git a/package.json b/package.json index ef721e4..9702e89 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "firebase": "^10.8.0", "lodash": "^4.17.21", "ngx-mat-select-search": "^7.0.5", + "qrcode": "^1.5.3", "rxfire": "^6.0.5", "rxjs": "~7.8.1", "swiper": "^11.0.6", diff --git a/src/app/modules/guest/guest-show-data.service.ts b/src/app/modules/guest/guest-show-data.service.ts new file mode 100644 index 0000000..3aa47f5 --- /dev/null +++ b/src/app/modules/guest/guest-show-data.service.ts @@ -0,0 +1,23 @@ +import {Injectable} from '@angular/core'; +import {BehaviorSubject, Observable} from 'rxjs'; +import {map} from 'rxjs/operators'; +import {DbService} from 'src/app/services/db.service'; +import {GuestShow} from './guest-show'; + +@Injectable({ + providedIn: 'root', +}) +export class GuestShowDataService { + public list$: BehaviorSubject = new BehaviorSubject([]); + private collection = 'guest'; + + public constructor(private dbService: DbService) { + this.dbService.col$(this.collection).subscribe(_ => this.list$.next(_)); + } + + public read$: (id: string) => Observable = (id: string): Observable => this.list$.pipe(map(_ => _.find(s => s.id === id) || null)); + public update$: (id: string, data: Partial) => Promise = async (id: string, data: Partial): Promise => + await this.dbService.doc(this.collection + '/' + id).update(data); + public add: (data: Partial) => Promise = async (data: Partial): Promise => (await this.dbService.col(this.collection).add(data)).id; + public delete: (id: string) => Promise = async (id: string): Promise => await this.dbService.doc(this.collection + '/' + id).delete(); +} diff --git a/src/app/modules/guest/guest-show.service.ts b/src/app/modules/guest/guest-show.service.ts new file mode 100644 index 0000000..c31981b --- /dev/null +++ b/src/app/modules/guest/guest-show.service.ts @@ -0,0 +1,32 @@ +import {Injectable} from '@angular/core'; +import {Show} from '../shows/services/show'; +import {Song} from '../songs/services/song'; +import {GuestShowDataService} from './guest-show-data.service'; +import {ShowService} from '../shows/services/show.service'; + +@Injectable({ + providedIn: 'root', +}) +export class GuestShowService { + public constructor( + private showService: ShowService, + private guestShowDataService: GuestShowDataService + ) {} + + public async share(show: Show, songs: Song[]): Promise { + const data = { + showType: show.showType, + date: show.date, + songs: songs, + }; + + if (!show.shareId) { + const shareId = await this.guestShowDataService.add(data); + await this.showService.update$(show.id, {shareId}); + } else { + await this.guestShowDataService.update$(show.shareId, data); + } + + return window.location.protocol + '//' + window.location.host + '/guest/' + show.shareId; + } +} diff --git a/src/app/modules/guest/guest-show.ts b/src/app/modules/guest/guest-show.ts new file mode 100644 index 0000000..35b9dd7 --- /dev/null +++ b/src/app/modules/guest/guest-show.ts @@ -0,0 +1,10 @@ +import firebase from 'firebase/compat/app'; +import {Song} from '../songs/services/song'; +import Timestamp = firebase.firestore.Timestamp; + +export interface GuestShow { + id: string; + showType: string; + date: Timestamp; + songs: Song[]; +} diff --git a/src/app/modules/guest/guest.component.html b/src/app/modules/guest/guest.component.html index e9e42c3..f07f363 100644 --- a/src/app/modules/guest/guest.component.html +++ b/src/app/modules/guest/guest.component.html @@ -1,11 +1,25 @@ -
- - - - - - - - - +
+
+
{{ show.showType|showType }}
+
{{ show.date.toDate() | date: 'dd.MM.yyyy' }}
+
+ +
+ + +
{{ song.title }}
+ + + + +
+
+
+ + diff --git a/src/app/modules/guest/guest.component.less b/src/app/modules/guest/guest.component.less index 740be92..385226c 100644 --- a/src/app/modules/guest/guest.component.less +++ b/src/app/modules/guest/guest.component.less @@ -1,12 +1,49 @@ +.page { + background: #0009; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + backdrop-filter: blur(8px); + + --swiper-scrollbar-bg-color: #fff3; + --swiper-scrollbar-drag-bg-color: #fff9; + --swiper-scrollbar-sides-offset: 20px; + --swiper-scrollbar-top: 100px; + --swiper-scrollbar-bottom: auto; +} + +.title { + color: white; + padding: 70px 20px 0; + display: flex; + justify-content: space-between; + align-items: center; + + .left { + font-size: 1.8em; + color: #fff; + } +} + +.song-title { + padding: 20px 20px 0; + font-size: 1.3em; +} + +.legal { + padding: 0 20px; + font-size: 0.6em; + color: #fff9; +} + .view { position: fixed; top: 0; bottom: 0; left: 0; right: 0; - background: #0003; - backdrop-filter: blur(10px); - z-index: 1; color: white; } @@ -14,3 +51,9 @@ app-song-text { margin: 20px; display: block; } + +.song-swipe { + margin-top: 100px; + margin-bottom: 50px; + min-height: calc(100vh - 150px); +} diff --git a/src/app/modules/guest/guest.component.ts b/src/app/modules/guest/guest.component.ts index d911684..e0c3764 100644 --- a/src/app/modules/guest/guest.component.ts +++ b/src/app/modules/guest/guest.component.ts @@ -1,11 +1,9 @@ import {Component} from '@angular/core'; -import {SongService} from '../songs/services/song.service'; -import {GlobalSettingsService} from '../../services/global-settings.service'; -import {Observable} from 'rxjs'; -import {filter, map, switchMap} from 'rxjs/operators'; -import {ShowSongService} from '../shows/services/show-song.service'; -import {GlobalSettings} from '../../services/global-settings'; -import {ShowService} from '../shows/services/show.service'; +import {GuestShowDataService} from './guest-show-data.service'; +import {ActivatedRoute} from '@angular/router'; +import {map, switchMap} from 'rxjs/operators'; +import {Song} from '../songs/services/song'; +import {ConfigService} from '../../services/config.service'; @Component({ selector: 'app-guest', @@ -13,20 +11,17 @@ import {ShowService} from '../shows/services/show.service'; styleUrls: ['./guest.component.less'], }) export class GuestComponent { - public songs$: Observable = this.globalSettingsService.get$.pipe( - filter(_ => !!_), - map(_ => _ as GlobalSettings), - map(_ => _.currentShow), - switchMap(_ => this.showSongService.list$(_).pipe(map(l => ({showSongs: l, currentShow: _})))), - switchMap(_ => this.showService.read$(_.currentShow).pipe(map(l => ({showSongs: _.showSongs, show: l})))), - filter(_ => !!_.showSongs), - map(_ => (_?.show ? _.show.order.map(o => _.showSongs.find(f => f.id === o) ?? _.showSongs[0]).map(m => m.text) : [])) + public show$ = this.currentRoute.params.pipe( + map(param => param.id as string), + switchMap(id => this.service.read$(id)) ); + public config$ = this.configService.get$(); public constructor( - private songService: SongService, - private showService: ShowService, - private globalSettingsService: GlobalSettingsService, - private showSongService: ShowSongService + private currentRoute: ActivatedRoute, + private service: GuestShowDataService, + private configService: ConfigService ) {} + + public trackBy = (index: number, show: Song) => show.id; } diff --git a/src/app/modules/guest/guest.module.ts b/src/app/modules/guest/guest.module.ts index 6689f91..09fe86b 100644 --- a/src/app/modules/guest/guest.module.ts +++ b/src/app/modules/guest/guest.module.ts @@ -1,11 +1,23 @@ -import {NgModule} from '@angular/core'; +import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {GuestComponent} from './guest.component'; import {RouterModule} from '@angular/router'; import {SongTextModule} from '../../widget-modules/components/song-text/song-text.module'; +import {ShowTypeTranslaterModule} from '../../widget-modules/pipes/show-type-translater/show-type-translater.module'; @NgModule({ declarations: [GuestComponent], - imports: [CommonModule, RouterModule.forChild([{path: '', component: GuestComponent}]), SongTextModule], + imports: [ + CommonModule, + RouterModule.forChild([ + { + path: ':id', + component: GuestComponent, + }, + ]), + SongTextModule, + ShowTypeTranslaterModule, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class GuestModule {} diff --git a/src/app/modules/shows/dialog/share-dialog/share-dialog.component.html b/src/app/modules/shows/dialog/share-dialog/share-dialog.component.html new file mode 100644 index 0000000..f6ee94f --- /dev/null +++ b/src/app/modules/shows/dialog/share-dialog/share-dialog.component.html @@ -0,0 +1,13 @@ + +
+ + +
diff --git a/src/app/modules/shows/dialog/share-dialog/share-dialog.component.less b/src/app/modules/shows/dialog/share-dialog/share-dialog.component.less new file mode 100644 index 0000000..dd07553 --- /dev/null +++ b/src/app/modules/shows/dialog/share-dialog/share-dialog.component.less @@ -0,0 +1,7 @@ +.qrcode { + width: 300px; + height: 300px; + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} diff --git a/src/app/modules/shows/dialog/share-dialog/share-dialog.component.spec.ts b/src/app/modules/shows/dialog/share-dialog/share-dialog.component.spec.ts new file mode 100644 index 0000000..b080612 --- /dev/null +++ b/src/app/modules/shows/dialog/share-dialog/share-dialog.component.spec.ts @@ -0,0 +1,22 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {ShareDialogComponent} from './share-dialog.component'; + +describe('ShareDialogComponent', () => { + let component: ShareDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ShareDialogComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ShareDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/modules/shows/dialog/share-dialog/share-dialog.component.ts b/src/app/modules/shows/dialog/share-dialog/share-dialog.component.ts new file mode 100644 index 0000000..2e708f5 --- /dev/null +++ b/src/app/modules/shows/dialog/share-dialog/share-dialog.component.ts @@ -0,0 +1,49 @@ +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogActions, MatDialogClose, MatDialogContent} from '@angular/material/dialog'; +import {MatButton} from '@angular/material/button'; +import QRCode from 'qrcode'; +import {AsyncPipe} from '@angular/common'; +import {ShowTypePipe} from '../../../../widget-modules/pipes/show-type-translater/show-type.pipe'; +import {Show} from '../../services/show'; + +export interface ShareDialogData { + url: string; + show: Show; +} + +@Component({ + selector: 'app-share-dialog', + standalone: true, + imports: [MatButton, MatDialogActions, MatDialogContent, MatDialogClose, AsyncPipe], + templateUrl: './share-dialog.component.html', + styleUrl: './share-dialog.component.less', +}) +export class ShareDialogComponent { + public qrCode: string; + + public constructor(@Inject(MAT_DIALOG_DATA) public data: ShareDialogData) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + QRCode.toDataURL(data.url, { + type: 'image/jpeg', + quality: 0.92, + width: 1280, + height: 1280, + color: { + dark: '#010414', + light: '#ffffff', + }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-return + }).then(_ => (this.qrCode = _)); + } + + public async share() { + if (navigator.clipboard) await navigator.clipboard.writeText(this.data.url); + + if (navigator.share) + await navigator.share({ + title: new ShowTypePipe().transform(this.data.show.showType), + text: new ShowTypePipe().transform(this.data.show.showType) + ' am ' + this.data.show.date.toDate().toLocaleString('de'), + url: this.data.url, + }); + } +} diff --git a/src/app/modules/shows/services/show.ts b/src/app/modules/shows/services/show.ts index 9d17b30..452a124 100644 --- a/src/app/modules/shows/services/show.ts +++ b/src/app/modules/shows/services/show.ts @@ -13,6 +13,7 @@ export interface Show { published: boolean; archived: boolean; order: string[]; + shareId: string; presentationSongId: string; presentationDynamicCaption: string; diff --git a/src/app/modules/shows/show/show.component.html b/src/app/modules/shows/show/show.component.html index 3980de7..95aabe6 100644 --- a/src/app/modules/shows/show/show.component.html +++ b/src/app/modules/shows/show/show.component.html @@ -6,7 +6,7 @@ show.date.toDate() | date: 'dd.MM.yyyy' }} - {{ getStatus(show) }}" > -

{{show.public ? 'öffentliche' : 'geschlossene'}} Veranstaltung von +

{{ show.public ? 'öffentliche' : 'geschlossene' }} Veranstaltung von

@@ -41,7 +41,8 @@
- Veröffentlichung zurückziehen + + Teilen + Ändern diff --git a/src/app/modules/shows/show/show.component.ts b/src/app/modules/shows/show/show.component.ts index 9765257..a65045b 100644 --- a/src/app/modules/shows/show/show.component.ts +++ b/src/app/modules/shows/show/show.component.ts @@ -19,6 +19,7 @@ import { faLock, faMagnifyingGlassMinus, faMagnifyingGlassPlus, + faShare, faSliders, faUser, faUsers, @@ -28,6 +29,8 @@ import {fade} from '../../../animations'; import {MatDialog} from '@angular/material/dialog'; import {ArchiveDialogComponent} from '../dialog/archive-dialog/archive-dialog.component'; import {closeFullscreen, openFullscreen} from '../../../services/fullscreen'; +import {GuestShowService} from '../../guest/guest-show.service'; +import {ShareDialogComponent} from '../dialog/share-dialog/share-dialog.component'; @Component({ selector: 'app-show', @@ -46,14 +49,13 @@ export class ShowComponent implements OnInit, OnDestroy { public faBoxOpen = faBoxOpen; public faPublish = faExternalLinkAlt; public faUnpublish = faLock; + public faShare = faShare; public faDownload = faFileDownload; public faSliders = faSliders; public faUser = faUser; public faUsers = faUsers; public faZoomIn = faMagnifyingGlassPlus; public faZoomOut = faMagnifyingGlassMinus; - public faPage = faFile; - public faLines = faFileLines; private subs: Subscription[] = []; public useSwiper = false; @@ -65,7 +67,8 @@ export class ShowComponent implements OnInit, OnDestroy { private docxService: DocxService, private router: Router, private cRef: ChangeDetectorRef, - public dialog: MatDialog + public dialog: MatDialog, + private guestShowService: GuestShowService ) {} public ngOnInit(): void { @@ -128,6 +131,11 @@ export class ShowComponent implements OnInit, OnDestroy { if (this.showId != null) await this.showService.update$(this.showId, {published}); } + public onShare = async (show: Show): Promise => { + const url = await this.guestShowService.share(show, this.orderedShowSongs(show)); + this.dialog.open(ShareDialogComponent, {data: {url, show}}); + }; + public getStatus(show: Show): string { if (show.published) { return 'veröffentlicht'; @@ -162,7 +170,7 @@ export class ShowComponent implements OnInit, OnDestroy { return show.order.map(_ => list.filter(f => f.id === _)[0]); } - public trackBy = (index: number, show: ShowSong) => show.id; + public trackBy = (_: number, show: ShowSong) => show.id; public async onChange(showId: string) { await this.router.navigateByUrl('/shows/' + showId + '/edit'); diff --git a/src/app/modules/songs/services/song.service.ts b/src/app/modules/songs/services/song.service.ts index da0d315..f139de9 100644 --- a/src/app/modules/songs/services/song.service.ts +++ b/src/app/modules/songs/services/song.service.ts @@ -25,7 +25,10 @@ export class SongService { // private list: Song[]; - public constructor(private songDataService: SongDataService, private userService: UserService) { + public constructor( + private songDataService: SongDataService, + private userService: UserService + ) { // importCCLI = (songs: Song[]) => this.updateFromCLI(songs); }