From 23d275aceca9253b88c9c98000f8f14696b97458 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Tue, 1 Mar 2022 21:23:12 -0500 Subject: [PATCH] Add Dexie for persistence; user management with dexie; this is the way --- web/package-lock.json | 362 ++------------------ web/package.json | 2 + web/src/app/Api.js | 7 +- web/src/app/Connection.js | 2 +- web/src/app/ConnectionManager.js | 6 +- web/src/app/Repository.js | 36 -- web/src/app/User.js | 9 - web/src/app/Users.js | 38 -- web/src/app/db.js | 15 + web/src/components/ActionBar.js | 1 - web/src/components/App.js | 27 +- web/src/components/IconSubscribeSettings.js | 4 +- web/src/components/Navigation.js | 4 +- web/src/components/Preferences.js | 251 +++++++++++--- web/src/components/SubscribeDialog.js | 13 +- web/src/components/styles.js | 2 +- 16 files changed, 285 insertions(+), 494 deletions(-) delete mode 100644 web/src/app/User.js delete mode 100644 web/src/app/Users.js create mode 100644 web/src/app/db.js diff --git a/web/package-lock.json b/web/package-lock.json index 8bdd9b6e..d601ae04 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -11,10 +11,10 @@ "@emotion/styled": "latest", "@mui/icons-material": "^5.4.2", "@mui/material": "latest", - "@mui/styles": "^5.4.2", + "dexie": "^3.2.1", + "dexie-react-hooks": "^1.1.1", "react": "latest", "react-dom": "latest", - "react-router-dom": "^6.2.1", "react-scripts": "^3.0.1" } }, @@ -2364,46 +2364,6 @@ } } }, - "node_modules/@mui/styles": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/@mui/styles/-/styles-5.4.2.tgz", - "integrity": "sha512-BX75fNHmRF51yove9dBkH28gpSFjClOPDEnUwLTghPYN913OsqViS/iuCd61dxzygtEEmmeYuWfQjxu/F6vF5g==", - "dependencies": { - "@babel/runtime": "^7.17.0", - "@emotion/hash": "^0.8.0", - "@mui/private-theming": "^5.4.2", - "@mui/types": "^7.1.2", - "@mui/utils": "^5.4.2", - "clsx": "^1.1.1", - "csstype": "^3.0.10", - "hoist-non-react-statics": "^3.3.2", - "jss": "^10.8.2", - "jss-plugin-camel-case": "^10.8.2", - "jss-plugin-default-unit": "^10.8.2", - "jss-plugin-global": "^10.8.2", - "jss-plugin-nested": "^10.8.2", - "jss-plugin-props-sort": "^10.8.2", - "jss-plugin-rule-value-function": "^10.8.2", - "jss-plugin-vendor-prefixer": "^10.8.2", - "prop-types": "^15.7.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@types/react": "^16.8.6 || ^17.0.0", - "react": "^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@mui/system": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.4.2.tgz", @@ -5683,15 +5643,6 @@ "node": ">=0.10.0" } }, - "node_modules/css-vendor": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", - "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", - "dependencies": { - "@babel/runtime": "^7.8.3", - "is-in-browser": "^1.0.2" - } - }, "node_modules/css-what": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", @@ -6174,6 +6125,24 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/dexie": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.1.tgz", + "integrity": "sha512-Y8oz3t2XC9hvjkP35B5I8rUkKKwM36GGRjWQCMjzIYScg7W+GHKDXobSYswkisW7CxL1/tKQtggMDsiWqDUc1g==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/dexie-react-hooks": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/dexie-react-hooks/-/dexie-react-hooks-1.1.1.tgz", + "integrity": "sha512-Cam5JP6PxHN564RvWEoe8cqLhosW0O4CAZ9XEVYeGHJBa6KEJlOpd9CUpV3kmU9dm2MrW97/lk7qkf1xpij7gA==", + "peerDependencies": { + "@types/react": ">=16", + "dexie": ">=3.1.0-alpha.1 <5.0.0", + "react": ">=16" + } + }, "node_modules/diff-sequences": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", @@ -8364,14 +8333,6 @@ "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" }, - "node_modules/history": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", - "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", - "dependencies": { - "@babel/runtime": "^7.7.6" - } - }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -8571,11 +8532,6 @@ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, - "node_modules/hyphenate-style-name": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", - "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -9168,11 +9124,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-in-browser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" - }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -10290,88 +10241,6 @@ "node": ">=0.6.0" } }, - "node_modules/jss": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.9.0.tgz", - "integrity": "sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "csstype": "^3.0.2", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/jss" - } - }, - "node_modules/jss-plugin-camel-case": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz", - "integrity": "sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "hyphenate-style-name": "^1.0.3", - "jss": "10.9.0" - } - }, - "node_modules/jss-plugin-default-unit": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz", - "integrity": "sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0" - } - }, - "node_modules/jss-plugin-global": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz", - "integrity": "sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0" - } - }, - "node_modules/jss-plugin-nested": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz", - "integrity": "sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0", - "tiny-warning": "^1.0.2" - } - }, - "node_modules/jss-plugin-props-sort": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz", - "integrity": "sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0" - } - }, - "node_modules/jss-plugin-rule-value-function": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz", - "integrity": "sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0", - "tiny-warning": "^1.0.2" - } - }, - "node_modules/jss-plugin-vendor-prefixer": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz", - "integrity": "sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "css-vendor": "^2.0.8", - "jss": "10.9.0" - } - }, "node_modules/jsx-ast-utils": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", @@ -13824,30 +13693,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, - "node_modules/react-router": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz", - "integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==", - "dependencies": { - "history": "^5.2.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz", - "integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==", - "dependencies": { - "history": "^5.2.0", - "react-router": "6.2.1" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, "node_modules/react-scripts": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.4.tgz", @@ -16357,11 +16202,6 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -19518,30 +19358,6 @@ "prop-types": "^15.7.2" } }, - "@mui/styles": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/@mui/styles/-/styles-5.4.2.tgz", - "integrity": "sha512-BX75fNHmRF51yove9dBkH28gpSFjClOPDEnUwLTghPYN913OsqViS/iuCd61dxzygtEEmmeYuWfQjxu/F6vF5g==", - "requires": { - "@babel/runtime": "^7.17.0", - "@emotion/hash": "^0.8.0", - "@mui/private-theming": "^5.4.2", - "@mui/types": "^7.1.2", - "@mui/utils": "^5.4.2", - "clsx": "^1.1.1", - "csstype": "^3.0.10", - "hoist-non-react-statics": "^3.3.2", - "jss": "^10.8.2", - "jss-plugin-camel-case": "^10.8.2", - "jss-plugin-default-unit": "^10.8.2", - "jss-plugin-global": "^10.8.2", - "jss-plugin-nested": "^10.8.2", - "jss-plugin-props-sort": "^10.8.2", - "jss-plugin-rule-value-function": "^10.8.2", - "jss-plugin-vendor-prefixer": "^10.8.2", - "prop-types": "^15.7.2" - } - }, "@mui/system": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.4.2.tgz", @@ -22155,15 +21971,6 @@ } } }, - "css-vendor": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", - "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", - "requires": { - "@babel/runtime": "^7.8.3", - "is-in-browser": "^1.0.2" - } - }, "css-what": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", @@ -22542,6 +22349,17 @@ } } }, + "dexie": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.1.tgz", + "integrity": "sha512-Y8oz3t2XC9hvjkP35B5I8rUkKKwM36GGRjWQCMjzIYScg7W+GHKDXobSYswkisW7CxL1/tKQtggMDsiWqDUc1g==" + }, + "dexie-react-hooks": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/dexie-react-hooks/-/dexie-react-hooks-1.1.1.tgz", + "integrity": "sha512-Cam5JP6PxHN564RvWEoe8cqLhosW0O4CAZ9XEVYeGHJBa6KEJlOpd9CUpV3kmU9dm2MrW97/lk7qkf1xpij7gA==", + "requires": {} + }, "diff-sequences": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", @@ -24243,14 +24061,6 @@ "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" }, - "history": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", - "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", - "requires": { - "@babel/runtime": "^7.7.6" - } - }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -24418,11 +24228,6 @@ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, - "hyphenate-style-name": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", - "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -24846,11 +24651,6 @@ "is-extglob": "^2.1.1" } }, - "is-in-browser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" - }, "is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -25729,84 +25529,6 @@ "verror": "1.10.0" } }, - "jss": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.9.0.tgz", - "integrity": "sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw==", - "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^3.0.2", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-camel-case": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz", - "integrity": "sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww==", - "requires": { - "@babel/runtime": "^7.3.1", - "hyphenate-style-name": "^1.0.3", - "jss": "10.9.0" - } - }, - "jss-plugin-default-unit": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz", - "integrity": "sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w==", - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0" - } - }, - "jss-plugin-global": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz", - "integrity": "sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ==", - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0" - } - }, - "jss-plugin-nested": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz", - "integrity": "sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA==", - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-props-sort": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz", - "integrity": "sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw==", - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0" - } - }, - "jss-plugin-rule-value-function": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz", - "integrity": "sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg==", - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.0", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-vendor-prefixer": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz", - "integrity": "sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA==", - "requires": { - "@babel/runtime": "^7.3.1", - "css-vendor": "^2.0.8", - "jss": "10.9.0" - } - }, "jsx-ast-utils": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", @@ -28599,23 +28321,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, - "react-router": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz", - "integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==", - "requires": { - "history": "^5.2.0" - } - }, - "react-router-dom": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz", - "integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==", - "requires": { - "history": "^5.2.0", - "react-router": "6.2.1" - } - }, "react-scripts": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.4.tgz", @@ -30620,11 +30325,6 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, - "tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", diff --git a/web/package.json b/web/package.json index 5a1e4456..cdb10465 100644 --- a/web/package.json +++ b/web/package.json @@ -12,6 +12,8 @@ "@emotion/styled": "latest", "@mui/icons-material": "^5.4.2", "@mui/material": "latest", + "dexie": "^3.2.1", + "dexie-react-hooks": "^1.1.1", "react": "latest", "react-dom": "latest", "react-scripts": "^3.0.1" diff --git a/web/src/app/Api.js b/web/src/app/Api.js index ff0af948..0625cb59 100644 --- a/web/src/app/Api.js +++ b/web/src/app/Api.js @@ -7,9 +7,11 @@ import { topicShortUrl, topicUrlJsonPollWithSince } from "./utils"; +import db from "./db"; class Api { - async poll(baseUrl, topic, since, user) { + async poll(baseUrl, topic, since) { + const user = await db.users.get(baseUrl); const shortUrl = topicShortUrl(baseUrl, topic); const url = (since) ? topicUrlJsonPollWithSince(baseUrl, topic, since) @@ -24,7 +26,8 @@ class Api { return messages; } - async publish(baseUrl, topic, user, message) { + async publish(baseUrl, topic, message) { + const user = await db.users.get(baseUrl); const url = topicUrl(baseUrl, topic); console.log(`[Api] Publishing message to ${url}`); await fetch(url, { diff --git a/web/src/app/Connection.js b/web/src/app/Connection.js index a4d612e6..d22640a5 100644 --- a/web/src/app/Connection.js +++ b/web/src/app/Connection.js @@ -85,7 +85,7 @@ class Connection { if (this.since) { params.push(`since=${this.since}`); } - if (this.user !== null) { + if (this.user) { const auth = encodeBase64Url(basicAuth(this.user.username, this.user.password)); params.push(`auth=${auth}`); } diff --git a/web/src/app/ConnectionManager.js b/web/src/app/ConnectionManager.js index 160d36d1..e9f66c31 100644 --- a/web/src/app/ConnectionManager.js +++ b/web/src/app/ConnectionManager.js @@ -6,7 +6,11 @@ class ConnectionManager { } refresh(subscriptions, users, onNotification) { + if (!subscriptions || !users) { + return; + } console.log(`[ConnectionManager] Refreshing connections`); + console.log(users); const subscriptionIds = subscriptions.ids(); const deletedIds = Array.from(this.connections.keys()).filter(id => !subscriptionIds.includes(id)); @@ -16,7 +20,7 @@ class ConnectionManager { if (added) { const baseUrl = subscription.baseUrl; const topic = subscription.topic; - const user = users.get(baseUrl); + const [user] = users.filter(user => user.baseUrl === baseUrl); const since = subscription.last; const connection = new Connection(id, baseUrl, topic, user, since, onNotification); this.connections.set(id, connection); diff --git a/web/src/app/Repository.js b/web/src/app/Repository.js index c3faf464..1f5989bd 100644 --- a/web/src/app/Repository.js +++ b/web/src/app/Repository.js @@ -1,7 +1,5 @@ import Subscription from "./Subscription"; import Subscriptions from "./Subscriptions"; -import Users from "./Users"; -import User from "./User"; class Repository { loadSubscriptions() { @@ -43,40 +41,6 @@ class Repository { localStorage.setItem('subscriptions', serialized); } - loadUsers() { - console.log(`[Repository] Loading users from localStorage`); - const users = new Users(); - users.loaded = true; - const serialized = localStorage.getItem('users'); - if (serialized === null) { - return users; - } - try { - JSON.parse(serialized).forEach(u => { - users.add(new User(u.baseUrl, u.username, u.password)); - }); - return users; - } catch (e) { - console.log(`[Repository] Unable to deserialize users: ${e.message}`); - return users; - } - } - - saveUsers(users) { - if (!users.loaded) { - return; // Avoid saving invalid state, triggered by initial useEffect hook - } - console.log(`[Repository] Saving users to localStorage`); - const serialized = JSON.stringify(users.map(user => { - return { - baseUrl: user.baseUrl, - username: user.username, - password: user.password - } - })); - localStorage.setItem('users', serialized); - } - loadSelectedSubscriptionId() { console.log(`[Repository] Loading selected subscription ID from localStorage`); const selectedSubscriptionId = localStorage.getItem('selectedSubscriptionId'); diff --git a/web/src/app/User.js b/web/src/app/User.js deleted file mode 100644 index f92a83dc..00000000 --- a/web/src/app/User.js +++ /dev/null @@ -1,9 +0,0 @@ -class User { - constructor(baseUrl, username, password) { - this.baseUrl = baseUrl; - this.username = username; - this.password = password; - } -} - -export default User; diff --git a/web/src/app/Users.js b/web/src/app/Users.js deleted file mode 100644 index 97225c91..00000000 --- a/web/src/app/Users.js +++ /dev/null @@ -1,38 +0,0 @@ -class Users { - constructor() { - this.loaded = false; // FIXME I hate this - this.users = new Map(); - } - - add(user) { - this.users.set(user.baseUrl, user); - return this; - } - - get(baseUrl) { - const user = this.users.get(baseUrl); - return (user) ? user : null; - } - - update(user) { - return this.add(user); - } - - remove(baseUrl) { - this.users.delete(baseUrl); - return this; - } - - map(cb) { - return Array.from(this.users.values()).map(cb); - } - - clone() { - const c = new Users(); - c.loaded = this.loaded; - c.users = new Map(this.users); - return c; - } -} - -export default Users; diff --git a/web/src/app/db.js b/web/src/app/db.js new file mode 100644 index 00000000..42d2f883 --- /dev/null +++ b/web/src/app/db.js @@ -0,0 +1,15 @@ +import Dexie from 'dexie'; + +// Uses Dexie.js +// https://dexie.org/docs/API-Reference#quick-reference +// +// Notes: +// - As per docs, we only declare the indexable columns, not all columns + +const db = new Dexie('ntfy'); + +db.version(1).stores({ + users: '&baseUrl, username', +}); + +export default db; diff --git a/web/src/components/ActionBar.js b/web/src/components/ActionBar.js index 6970f4f4..0c1ef28e 100644 --- a/web/src/components/ActionBar.js +++ b/web/src/components/ActionBar.js @@ -37,7 +37,6 @@ const ActionBar = (props) => { {props.selectedSubscription !== null && } diff --git a/web/src/components/App.js b/web/src/components/App.js index 0ff4d08f..20a5f138 100644 --- a/web/src/components/App.js +++ b/web/src/components/App.js @@ -12,19 +12,20 @@ import connectionManager from "../app/ConnectionManager"; import Subscriptions from "../app/Subscriptions"; import Navigation from "./Navigation"; import ActionBar from "./ActionBar"; -import Users from "../app/Users"; import notificationManager from "../app/NotificationManager"; import NoTopics from "./NoTopics"; import Preferences from "./Preferences"; +import db from "../app/db"; +import {useLiveQuery} from "dexie-react-hooks"; // TODO subscribe dialog: // - check/use existing user // - add baseUrl -// TODO user management // TODO embed into ntfy server // TODO make default server functional // TODO indexeddb for notifications + subscriptions // TODO business logic with callbacks +// TODO connection indicator in subscription list const App = () => { console.log(`[App] Rendering main view`); @@ -32,21 +33,18 @@ const App = () => { const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); const [prefsOpen, setPrefsOpen] = useState(false); const [subscriptions, setSubscriptions] = useState(new Subscriptions()); - const [users, setUsers] = useState(new Users()); const [selectedSubscription, setSelectedSubscription] = useState(null); const [notificationsGranted, setNotificationsGranted] = useState(notificationManager.granted()); + const users = useLiveQuery(() => db.users.toArray()); const handleSubscriptionClick = (subscriptionId) => { setSelectedSubscription(subscriptions.get(subscriptionId)); setPrefsOpen(false); } - const handleSubscribeSubmit = (subscription, user) => { + const handleSubscribeSubmit = (subscription) => { console.log(`[App] New subscription: ${subscription.id}`); - if (user !== null) { - setUsers(prev => prev.add(user).clone()); - } setSubscriptions(prev => prev.add(subscription).clone()); setSelectedSubscription(subscription); - poll(subscription, user); + poll(subscription); handleRequestPermission(); }; const handleDeleteNotification = (subscriptionId, notificationId) => { @@ -80,9 +78,9 @@ const App = () => { setPrefsOpen(true); setSelectedSubscription(null); }; - const poll = (subscription, user) => { + const poll = (subscription) => { const since = subscription.last; - api.poll(subscription.baseUrl, subscription.topic, since, user) + api.poll(subscription.baseUrl, subscription.topic, since) .then(notifications => { setSubscriptions(prev => { subscription.addNotifications(notifications); @@ -94,12 +92,10 @@ const App = () => { // Define hooks: Note that the order of the hooks is important. The "loading" hooks // must be before the "saving" hooks. useEffect(() => { - // Load subscriptions and users + // Load subscriptions const subscriptions = repository.loadSubscriptions(); const selectedSubscriptionId = repository.loadSelectedSubscriptionId(); - const users = repository.loadUsers(); setSubscriptions(subscriptions); - setUsers(users); // Set selected subscription const maybeSelectedSubscription = subscriptions.get(selectedSubscriptionId); @@ -109,8 +105,7 @@ const App = () => { // Poll all subscriptions subscriptions.forEach((subscriptionId, subscription) => { - const user = users.get(subscription.baseUrl); // May be null - poll(subscription, user); + poll(subscription); }); }, [/* initial render */]); useEffect(() => { @@ -127,7 +122,6 @@ const App = () => { connectionManager.refresh(subscriptions, users, handleNotification); }, [subscriptions, users]); useEffect(() => repository.saveSubscriptions(subscriptions), [subscriptions]); - useEffect(() => repository.saveUsers(users), [users]); useEffect(() => { const subscriptionId = (selectedSubscription) ? selectedSubscription.id : ""; repository.saveSelectedSubscriptionId(subscriptionId) @@ -140,7 +134,6 @@ const App = () => { setMobileDrawerOpen(!mobileDrawerOpen)} diff --git a/web/src/components/IconSubscribeSettings.js b/web/src/components/IconSubscribeSettings.js index c8a3603a..1750e612 100644 --- a/web/src/components/IconSubscribeSettings.js +++ b/web/src/components/IconSubscribeSettings.js @@ -14,7 +14,6 @@ import api from "../app/Api"; const IconSubscribeSettings = (props) => { const [open, setOpen] = useState(false); const anchorRef = useRef(null); - const users = props.users; const handleToggle = () => { setOpen((prevOpen) => !prevOpen); @@ -40,8 +39,7 @@ const IconSubscribeSettings = (props) => { const handleSendTestMessage = () => { const baseUrl = props.subscription.baseUrl; const topic = props.subscription.topic; - const user = users.get(baseUrl); // May be null - api.publish(baseUrl, topic, user, + api.publish(baseUrl, topic, `This is a test notification sent by the ntfy Web UI at ${new Date().toString()}.`); // FIXME result ignored setOpen(false); } diff --git a/web/src/components/Navigation.js b/web/src/components/Navigation.js index f54313b0..f586a6f1 100644 --- a/web/src/components/Navigation.js +++ b/web/src/components/Navigation.js @@ -57,9 +57,9 @@ const NavList = (props) => { setSubscribeDialogOpen(false); setSubscribeDialogKey(prev => prev+1); } - const handleSubscribeSubmit = (subscription, user) => { + const handleSubscribeSubmit = (subscription) => { handleSubscribeReset(); - props.onSubscribeSubmit(subscription, user); + props.onSubscribeSubmit(subscription); } const showSubscriptionsList = props.subscriptions.size() > 0; const showGrantPermissionsBox = props.subscriptions.size() > 0 && !props.notificationsGranted; diff --git a/web/src/components/Preferences.js b/web/src/components/Preferences.js index e7d712fa..79bf8e66 100644 --- a/web/src/components/Preferences.js +++ b/web/src/components/Preferences.js @@ -1,6 +1,18 @@ import * as React from 'react'; -import {useState} from 'react'; -import {FormControl, Select, Stack, Table, TableBody, TableCell, TableHead, TableRow} from "@mui/material"; +import {useEffect, useState} from 'react'; +import { + CardActions, + CardContent, + FormControl, + Select, + Stack, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + useMediaQuery +} from "@mui/material"; import Typography from "@mui/material/Typography"; import Paper from "@mui/material/Paper"; import repository from "../app/Repository"; @@ -11,6 +23,15 @@ import IconButton from "@mui/material/IconButton"; import Container from "@mui/material/Container"; import TextField from "@mui/material/TextField"; import MenuItem from "@mui/material/MenuItem"; +import Card from "@mui/material/Card"; +import Button from "@mui/material/Button"; +import db from "../app/db"; +import {useLiveQuery} from "dexie-react-hooks"; +import theme from "./theme"; +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import DialogActions from "@mui/material/DialogActions"; const Preferences = (props) => { return ( @@ -26,7 +47,7 @@ const Preferences = (props) => { const Notifications = (props) => { return ( - + Notifications @@ -34,7 +55,7 @@ const Notifications = (props) => { - + ); }; @@ -66,7 +87,7 @@ const DeleteAfter = () => { repository.setDeleteAfter(ev.target.value); } return ( - +