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